From 96fbebbeff11c7669fdd72e820ef6403909435e2 Mon Sep 17 00:00:00 2001 From: Maple Xu Date: Mon, 13 Apr 2026 16:11:42 -0400 Subject: [PATCH 1/2] AI-61: Strip unset None fields from ADK plugin payloads Use ToJsonOptions(exclude_unset=True) in the ADK plugin's payload converter, matching the OpenAI plugin's pattern. This reduces LlmRequest payloads from 4-8KB of mostly nulls to just the fields that were set. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../contrib/google_adk_agents/_plugin.py | 14 ++++++-- .../test_google_adk_agents.py | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/temporalio/contrib/google_adk_agents/_plugin.py b/temporalio/contrib/google_adk_agents/_plugin.py index 03cb78998..760b9468d 100644 --- a/temporalio/contrib/google_adk_agents/_plugin.py +++ b/temporalio/contrib/google_adk_agents/_plugin.py @@ -10,7 +10,8 @@ from temporalio.contrib.google_adk_agents._mcp import TemporalMcpToolSetProvider from temporalio.contrib.google_adk_agents._model import invoke_model from temporalio.contrib.pydantic import ( - PydanticPayloadConverter as _DefaultPydanticPayloadConverter, + PydanticPayloadConverter, + ToJsonOptions, ) from temporalio.converter import DataConverter, DefaultPayloadConverter from temporalio.plugin import SimplePlugin @@ -112,10 +113,17 @@ def _configure_data_converter( ) -> DataConverter: if converter is None: return DataConverter( - payload_converter_class=_DefaultPydanticPayloadConverter + payload_converter_class=_AdkPayloadConverter ) elif converter.payload_converter_class is DefaultPayloadConverter: return dataclasses.replace( - converter, payload_converter_class=_DefaultPydanticPayloadConverter + converter, payload_converter_class=_AdkPayloadConverter ) return converter + + +class _AdkPayloadConverter(PydanticPayloadConverter): + """PayloadConverter for Google ADK that strips unset None fields.""" + + def __init__(self) -> None: + super().__init__(ToJsonOptions(exclude_unset=True)) diff --git a/tests/contrib/google_adk_agents/test_google_adk_agents.py b/tests/contrib/google_adk_agents/test_google_adk_agents.py index d7ccd4699..c73f21fe0 100644 --- a/tests/contrib/google_adk_agents/test_google_adk_agents.py +++ b/tests/contrib/google_adk_agents/test_google_adk_agents.py @@ -14,6 +14,7 @@ """Integration tests for ADK Temporal support.""" +import json import logging import os import uuid @@ -855,3 +856,37 @@ async def test_activity_tool_supports_complex_inputs_via_adk(client: Client): ), "annotate_trip": "SFO->LAX:3", } + + +def test_unset_none_fields_stripped() -> None: + """ADK plugin converter strips unset None fields from Pydantic payloads.""" + plugin = GoogleAdkPlugin() + converter = plugin._configure_data_converter(None) + request = LlmRequest( + model="gemini-2.0-flash", + contents=[Content(parts=[Part(text="hello")])], + ) + payloads = converter.payload_converter.to_payloads([request]) + serialized = json.loads(payloads[0].data) + + assert serialized["model"] == "gemini-2.0-flash" + assert "contents" in serialized + for field in ("cache_config", "cache_metadata", + "cacheable_contents_token_count", "previous_interaction_id"): + assert field not in serialized, f"Unset field {field!r} should be stripped" + + +def test_explicitly_set_none_preserved() -> None: + """Explicitly-set None is preserved (exclude_unset, not exclude_none).""" + plugin = GoogleAdkPlugin() + converter = plugin._configure_data_converter(None) + request = LlmRequest( + model="gemini-2.0-flash", + contents=[Content(parts=[Part(text="hello")])], + cache_config=None, + ) + payloads = converter.payload_converter.to_payloads([request]) + serialized = json.loads(payloads[0].data) + + assert "cache_config" in serialized, "Explicitly-set None should be preserved" + assert serialized["cache_config"] is None From 3c919ee8ea00b1d1c1465c6d5b25624197e81daa Mon Sep 17 00:00:00 2001 From: Maple Xu Date: Mon, 13 Apr 2026 17:25:14 -0400 Subject: [PATCH 2/2] AI-61: Fix ruff formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- temporalio/contrib/google_adk_agents/_plugin.py | 4 +--- tests/contrib/google_adk_agents/test_google_adk_agents.py | 8 ++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/temporalio/contrib/google_adk_agents/_plugin.py b/temporalio/contrib/google_adk_agents/_plugin.py index 760b9468d..9be321398 100644 --- a/temporalio/contrib/google_adk_agents/_plugin.py +++ b/temporalio/contrib/google_adk_agents/_plugin.py @@ -112,9 +112,7 @@ def _configure_data_converter( self, converter: DataConverter | None ) -> DataConverter: if converter is None: - return DataConverter( - payload_converter_class=_AdkPayloadConverter - ) + return DataConverter(payload_converter_class=_AdkPayloadConverter) elif converter.payload_converter_class is DefaultPayloadConverter: return dataclasses.replace( converter, payload_converter_class=_AdkPayloadConverter diff --git a/tests/contrib/google_adk_agents/test_google_adk_agents.py b/tests/contrib/google_adk_agents/test_google_adk_agents.py index 7f0118191..e35d58ea6 100644 --- a/tests/contrib/google_adk_agents/test_google_adk_agents.py +++ b/tests/contrib/google_adk_agents/test_google_adk_agents.py @@ -979,8 +979,12 @@ def test_unset_none_fields_stripped() -> None: assert serialized["model"] == "gemini-2.0-flash" assert "contents" in serialized - for field in ("cache_config", "cache_metadata", - "cacheable_contents_token_count", "previous_interaction_id"): + for field in ( + "cache_config", + "cache_metadata", + "cacheable_contents_token_count", + "previous_interaction_id", + ): assert field not in serialized, f"Unset field {field!r} should be stripped"