Skip to content

Commit 8c60d99

Browse files
Brumbelowcopybara-github
authored andcommitted
fix: guard decode_model against non-dict session values
Merge #6383 Closes #6348 PiperOrigin-RevId: 947276063
1 parent 27edb1a commit 8c60d99

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/google/adk/sessions/_session_util.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ def decode_model(
2929
data: Optional[dict[str, Any]], model_cls: Type[M]
3030
) -> Optional[M]:
3131
"""Decodes a pydantic model object from a JSON dictionary."""
32-
if data is None:
32+
# Guard against primitive non-dict values (e.g. a legacy/corrupted "null" string
33+
# persisted in place of SQL NULL). Passing those to model_validate would
34+
# raise a ValidationError and break session replay in get_session().
35+
# We allow dicts and other objects (like PydanticNamespace in tests).
36+
if data is None or isinstance(
37+
data, (str, int, float, bool, list, set, tuple, bytes)
38+
):
3339
return None
3440
return model_cls.model_validate(data)
3541

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from google.adk.sessions._session_util import decode_model
16+
from google.genai import types
17+
18+
19+
def test_decode_model_returns_none_for_none():
20+
assert decode_model(None, types.Content) is None
21+
22+
23+
def test_decode_model_validates_dict():
24+
result = decode_model(
25+
{"role": "user", "parts": [{"text": "hello"}]}, types.Content
26+
)
27+
assert isinstance(result, types.Content)
28+
assert result.role == "user"
29+
assert result.parts[0].text == "hello"
30+
31+
32+
def test_decode_model_returns_none_for_non_dict_value():
33+
# A transcription field persisted as the JSON string "null" instead of SQL
34+
# NULL should decode to None rather than crash session replay.
35+
assert decode_model("null", types.Transcription) is None

0 commit comments

Comments
 (0)