Skip to content

Commit 5515800

Browse files
GWealecopybara-github
authored andcommitted
fix: catch RecursionError in safe JSON serialization helpers
Applies the same guard to all three safe-JSON helpers (lite_llm and the two telemetry serializers) so deeply nested input cannot escape as an uncaught RecursionError. Close #5411 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933964930
1 parent 978be4b commit 5515800

5 files changed

Lines changed: 25 additions & 3 deletions

File tree

src/google/adk/models/lite_llm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ def _safe_json_serialize(obj) -> str:
655655
try:
656656
# Try direct JSON serialization first
657657
return json.dumps(obj, ensure_ascii=False)
658-
except (TypeError, ValueError, OverflowError):
658+
except (TypeError, ValueError, OverflowError, RecursionError):
659659
return str(obj)
660660

661661

src/google/adk/telemetry/_experimental_semconv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _safe_json_serialize_no_whitespaces(obj: object) -> str:
190190
ensure_ascii=False,
191191
default=lambda o: '<not serializable>',
192192
)
193-
except (TypeError, ValueError, OverflowError):
193+
except (TypeError, ValueError, OverflowError, RecursionError):
194194
return '<not serializable>'
195195

196196

src/google/adk/telemetry/_serialization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def safe_json_serialize(obj: object) -> str:
3737
return json.dumps(
3838
obj, ensure_ascii=False, default=lambda o: "<not serializable>"
3939
)
40-
except (TypeError, ValueError, OverflowError):
40+
except (TypeError, ValueError, OverflowError, RecursionError):
4141
return "<not serializable>"
4242

4343

tests/unittests/models/test_litellm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,14 @@ def test_safe_json_serialize_circular_list_falls_back_to_str():
709709
assert isinstance(_safe_json_serialize(obj), str)
710710

711711

712+
def test_safe_json_serialize_recursion_error_falls_back_to_str():
713+
with patch(
714+
"google.adk.models.lite_llm.json.dumps",
715+
side_effect=RecursionError("maximum recursion depth"),
716+
):
717+
assert _safe_json_serialize({"a": 1}) == str({"a": 1})
718+
719+
712720
MULTIPLE_FUNCTION_CALLS_STREAM = [
713721
ModelResponseStream(
714722
choices=[

tests/unittests/telemetry/test_spans.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,20 @@ def test_safe_json_serialize_no_whitespaces_circular_dict_returns_not_serializab
14551455
assert _safe_json_serialize_no_whitespaces(obj) == '<not serializable>'
14561456

14571457

1458+
def test_safe_json_serialize_recursion_error_returns_not_serializable():
1459+
with mock.patch.object(
1460+
json, 'dumps', side_effect=RecursionError('maximum recursion depth')
1461+
):
1462+
assert safe_json_serialize({'a': 1}) == '<not serializable>'
1463+
1464+
1465+
def test_safe_json_serialize_no_whitespaces_recursion_error_returns_not_serializable():
1466+
with mock.patch.object(
1467+
json, 'dumps', side_effect=RecursionError('maximum recursion depth')
1468+
):
1469+
assert _safe_json_serialize_no_whitespaces({'a': 1}) == '<not serializable>'
1470+
1471+
14581472
def test_use_extra_generate_content_attributes_upgraded_version(monkeypatch):
14591473
# Arrange: Mock the presence of the new event-only context key in the contrib module
14601474
from opentelemetry.instrumentation import google_genai

0 commit comments

Comments
 (0)