Skip to content

GoogleSearchAgentTool parallel responses fail to serialize deferred GroundingMetadata (MockValSer) #6848

Description

@andrewrfitz

🔴 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:

  1. Install the affected versions:

    python -m pip install "google-adk==2.7.1" "google-genai==2.19.0" "pydantic==2.13.4"
  2. Run the snippet under Minimal Reproduction Code below. It requires no
    model call or API key.

  3. Observe the PydanticSerializationError above.

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: nested GroundingMetadata has a built
    SchemaSerializer; the merge succeeds.
  • google-genai==2.18.0: the shared model config includes defer_build=True;
    nested GroundingMetadata retains a MockValSer; 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 via types.Candidate.model_validate(...),
    which mirrors the response-validation path, leaves the MockValSer.
  • Once anything else in the process builds this class serializer, the failure
    disappears for subsequent calls.

Possible fixes, in rough order of narrowness:

  1. Build types.GroundingMetadata before ADK propagates or serializes it, for
    example with types.GroundingMetadata.model_rebuild(force=True). Removing
    defer_build for this model in google-genai would also resolve the root
    behavior.
  2. Store grounding metadata in state_delta in serialized form, then
    revalidate it where base_llm_flow attaches it to the response.
  3. Make _make_json_serializable resilient 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)
# PydanticSerializationError

The 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 GroundingMetadata is present in one of two
    or more function-response events being merged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions