Repository navigation
GoogleSearchAgentTool parallel responses fail to serialize deferred GroundingMetadata (MockValSer) #6848
Description
🔴 Required Information
Describe the Bug:
AgentTool(propagate_grounding_metadata=True)—which GoogleSearchAgentTool
enables—stores a raw google.genai.types.GroundingMetadata instance in session
state under temp:_adk_grounding_metadata. That value reaches
EventActions.state_delta, which is typed as dict[str, Any], so event
serialization relies on Pydantic's duck-typed serialization of the value.
google-genai 2.18.0 and newer configure their generated models with
defer_build=True. A GroundingMetadata produced by nested validation (for
example, as part of Candidate validation, as happens for model responses)
leaves GroundingMetadata.__pydantic_serializer__ as a MockValSer.
Serializing the event then raises:
pydantic_core._pydantic_core.PydanticSerializationError: Error calling function `_serialize_state_delta`: TypeError: 'MockValSer' object is not an instance of 'SchemaSerializer'
The _make_json_serializable fallback introduced by #4748 does not recover
from this case. pydantic_core.to_jsonable_python(value, serialize_unknown=True) raises the same TypeError before
serialize_unknown can apply, so the exception escapes
_serialize_state_delta.
This surfaces in merge_parallel_function_response_events in
google/adk/flows/llm_flows/functions.py, which calls
event.actions.model_dump(exclude_none=True, by_alias=True). When a search call
runs in parallel with another function call, the merge raises and aborts the
invocation.
This is related to #4748 and prior grounding-metadata persistence reports such
as #5840, but it is a distinct failure mode: the value is a valid Pydantic
model whose deferred serializer has not been built.
Steps to Reproduce:
-
Install the affected versions:
python -m pip install "google-adk==2.7.1" "google-genai==2.19.0" "pydantic==2.13.4"
-
Run the snippet under Minimal Reproduction Code below. It requires no
model call or API key. -
Observe the
PydanticSerializationErrorabove.
Expected Behavior:
Serializing an event whose state_delta contains grounding metadata propagated
by ADK succeeds, and parallel function-response merging preserves both tool
results. ADK-created state should not depend on whether a deferred class
serializer happened to be built earlier in the process.
Observed Behavior:
EventActions.model_dump(...) raises from
merge_parallel_function_response_events, and the invocation terminates
without a final response.
Environment Details:
- ADK Library Version: 2.7.1 (also reproduced on 2.7.0)
- Desktop OS: macOS 26.6 (also reproduced on Linux)
- Python Version: 3.14.6 (also reproduced on 3.12.6)
google-genai: 2.19.0 (also reproduced on 2.18.1)pydantic: 2.13.4 /pydantic-core: 2.46.4
Model Information:
- Are you using LiteLLM: No
- Which model is being used: N/A—the minimal reproduction performs no model
call and requires no API key.
🟡 Optional Information
Regression:
This is a google-genai 2.18.0 regression:
google-genai==2.17.0: nestedGroundingMetadatahas a built
SchemaSerializer; the merge succeeds.google-genai==2.18.0: the shared model config includesdefer_build=True;
nestedGroundingMetadataretains aMockValSer; the merge fails.
The failure also reproduces with google-adk==2.6.0 plus
google-genai==2.18.1, so it is not specific to ADK 2.7.x. ADK is affected
because it propagates the raw deferred model through state_delta and then
serializes that state while merging parallel tool responses.
Logs:
Failed to serialize `state_delta`; some values are not JSON-serializable and
will be replaced with a string representation in the persisted event.
Traceback (most recent call last):
File "google/adk/events/event_actions.py", line 106, in _serialize_state_delta
return cast(dict[str, Any], handler(value))
TypeError: 'MockValSer' object is not an instance of 'SchemaSerializer'
During fallback handling, `to_jsonable_python(...)` raises the same TypeError.
The resulting PydanticSerializationError escapes the parallel-response merge.
Additional Context:
Two details make this easy to miss in tests:
- Constructing
types.GroundingMetadata(...)directly builds the serializer as
a side effect. Nested validation viatypes.Candidate.model_validate(...),
which mirrors the response-validation path, leaves theMockValSer. - Once anything else in the process builds this class serializer, the failure
disappears for subsequent calls.
Possible fixes, in rough order of narrowness:
- Build
types.GroundingMetadatabefore ADK propagates or serializes it, for
example withtypes.GroundingMetadata.model_rebuild(force=True). Removing
defer_buildfor this model ingoogle-genaiwould also resolve the root
behavior. - Store grounding metadata in
state_deltain serialized form, then
revalidate it wherebase_llm_flowattaches it to the response. - Make
_make_json_serializableresilient per value so the fallback cannot
itself abort event serialization. This is defense in depth and would
degrade the metadata rather than preserve it.
Happy to send a PR for the preferred direction.
Minimal Reproduction Code:
from google.adk.events import Event, EventActions
from google.adk.flows.llm_flows.functions import (
merge_parallel_function_response_events,
)
from google.genai import types
candidate = types.Candidate.model_validate(
{"groundingMetadata": {"webSearchQueries": ["some query"]}}
)
print(type(types.GroundingMetadata.__pydantic_serializer__).__name__)
# MockValSer
def response_event(name: str, state_delta: dict) -> Event:
return Event(
invocation_id="inv",
author="my_agent",
content=types.Content(
role="user",
parts=[
types.Part.from_function_response(
name=name,
response={"ok": True},
)
],
),
actions=EventActions(state_delta=state_delta),
)
events = [
response_event(
"search_agent",
{"temp:_adk_grounding_metadata": candidate.grounding_metadata},
),
response_event("other_tool", {"temp:other": 1}),
]
merge_parallel_function_response_events(events)
# PydanticSerializationErrorThe fallback also fails directly:
from pydantic_core import to_jsonable_python
to_jsonable_python(
{"k": candidate.grounding_metadata},
serialize_unknown=True,
)
# TypeError: 'MockValSer' object is not an instance of 'SchemaSerializer'How often has this issue occurred?:
- Always (100%) when a deferred
GroundingMetadatais present in one of two
or more function-response events being merged.
Activity
ALDRIN121 commentedon Aug 22, 2026
Great analysis — the
defer_build/MockValSerfailure mode is nasty because it only surfaces in parallel merges.I see PR #6849 fixes this case at the source (recursive
model_rebuildfor deferred Pydantic models), but the issue's own observation is that the_make_json_serializablefallback "does not recover from this case" —to_jsonable_python(value, serialize_unknown=True)raises beforeserialize_unknowncan apply. So any value that trips pydantic-core still aborts_serialize_state_deltaand kills the invocation.I'd like to contribute the complementary defense-in-depth slice: make
_make_json_serializableper-value resilient (walk dicts/lists, convert each leaf under try/except, fall back toreprfor values that raise) so the fallback can never crash event serialization, plus a unit test with a deferred-serializer model. Zero overlap with #6849's change — it hardens exactly the fallback path the issue calls out, whatever the next exotic value turns out to be.@andrewrfitz — happy to coordinate: if you'd rather keep #6849 self-contained I'll hold and open this as a follow-up once yours lands, or open it alongside now if the maintainers prefer the split.
andrewrfitz commentedon Aug 25, 2026
Thanks @ALDRIN121! If it's zero overlap, I'm happy to just keep #6849 separate and have your complimentary PR merged as well
llalitkumarrr commentedon Aug 25, 2026
Hello @andrewrfitz,
Thank you for your contribution. We will review your PR and merge it once all checks pass and the reviewer provides their approval. We will get back to you if any additional information is required, we appreciate your patience in the meantime.
ALDRIN121 commentedon Aug 28, 2026
Complementary defense-in-depth PR is up: #6932 — makes
_make_json_serializableresilient per value (try/except per leaf,reprfallback, warning log with affected paths) so a deferred Pydantic serializer can never abort the invocation. Healthy output is byte-identical toto_jsonable_python(parity-tested). Thanks @andrewrfitz for the green light and @llalitkumarrr for the acknowledgment.fix(events): make state serialization fallback resilient per value
DeanChensj commentedon Oct 14, 2026
Hi @andrewrfitz, thank you for reporting this issue!
This has been resolved by PR #6932 and merged into the main branch in commit a119dd7.
We are closing this issue as resolved. If you find that the issue persists on the latest ADK release or if this was closed in error, please feel free to reopen it or leave a comment! Thank you again for your contribution.
github-actions commentedon Oct 14, 2026
Are you satisfied with the resolution of your issue?
Yes
No