Skip to content

Commit 70a7add

Browse files
GWealecopybara-github
authored andcommitted
fix: catch ValueError in safe-JSON serializers for circular refs
Same fix applied to the matching helpers in google.adk.telemetry. Close #5412 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 905265632
1 parent 34c7505 commit 70a7add

5 files changed

Lines changed: 43 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
@@ -543,7 +543,7 @@ def _safe_json_serialize(obj) -> str:
543543
try:
544544
# Try direct JSON serialization first
545545
return json.dumps(obj, ensure_ascii=False)
546-
except (TypeError, OverflowError):
546+
except (TypeError, ValueError, OverflowError):
547547
return str(obj)
548548

549549

src/google/adk/telemetry/_experimental_semconv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def _safe_json_serialize_no_whitespaces(obj) -> str:
139139
ensure_ascii=False,
140140
default=lambda o: '<not serializable>',
141141
)
142-
except (TypeError, OverflowError):
142+
except (TypeError, ValueError, OverflowError):
143143
return '<not serializable>'
144144

145145

src/google/adk/telemetry/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def _safe_json_serialize(obj) -> str:
128128
return json.dumps(
129129
obj, ensure_ascii=False, default=lambda o: '<not serializable>'
130130
)
131-
except (TypeError, OverflowError):
131+
except (TypeError, ValueError, OverflowError):
132132
return '<not serializable>'
133133

134134

tests/unittests/models/test_litellm.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from google.adk.models.lite_llm import _model_response_to_generate_content_response
4646
from google.adk.models.lite_llm import _parse_tool_calls_from_text
4747
from google.adk.models.lite_llm import _redirect_litellm_loggers_to_stdout
48+
from google.adk.models.lite_llm import _safe_json_serialize
4849
from google.adk.models.lite_llm import _schema_to_dict
4950
from google.adk.models.lite_llm import _split_message_content_and_tool_calls
5051
from google.adk.models.lite_llm import _THOUGHT_SIGNATURE_SEPARATOR
@@ -680,6 +681,31 @@ def test_schema_to_dict_filters_none_enum_values():
680681
]
681682

682683

684+
def test_safe_json_serialize_serializable_object():
685+
assert _safe_json_serialize({"a": 1, "b": [2, 3]}) == '{"a": 1, "b": [2, 3]}'
686+
687+
688+
def test_safe_json_serialize_non_serializable_object_falls_back_to_str():
689+
class _NotJsonable:
690+
691+
def __repr__(self):
692+
return "<not jsonable>"
693+
694+
assert _safe_json_serialize(_NotJsonable()) == "<not jsonable>"
695+
696+
697+
def test_safe_json_serialize_circular_dict_falls_back_to_str():
698+
obj = {}
699+
obj["self"] = obj
700+
assert isinstance(_safe_json_serialize(obj), str)
701+
702+
703+
def test_safe_json_serialize_circular_list_falls_back_to_str():
704+
obj = []
705+
obj.append(obj)
706+
assert isinstance(_safe_json_serialize(obj), str)
707+
708+
683709
MULTIPLE_FUNCTION_CALLS_STREAM = [
684710
ModelResponseStream(
685711
choices=[

tests/unittests/telemetry/test_spans.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
from google.adk.models.llm_request import LlmRequest
2626
from google.adk.models.llm_response import LlmResponse
2727
from google.adk.sessions.in_memory_session_service import InMemorySessionService
28+
from google.adk.telemetry._experimental_semconv import _safe_json_serialize_no_whitespaces
29+
from google.adk.telemetry.tracing import _safe_json_serialize
2830
from google.adk.telemetry.tracing import ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS
2931
from google.adk.telemetry.tracing import GCP_MCP_SERVER_DESTINATION_ID
3032
from google.adk.telemetry.tracing import trace_agent_invocation
@@ -1383,3 +1385,15 @@ def test_trace_tool_call_with_standard_error(
13831385
mock.call('error.type', 'ValueError')
13841386
in mock_span_fixture.set_attribute.call_args_list
13851387
)
1388+
1389+
1390+
def test_safe_json_serialize_circular_dict_returns_not_serializable():
1391+
obj = {}
1392+
obj['self'] = obj
1393+
assert _safe_json_serialize(obj) == '<not serializable>'
1394+
1395+
1396+
def test_safe_json_serialize_no_whitespaces_circular_dict_returns_not_serializable():
1397+
obj = {}
1398+
obj['self'] = obj
1399+
assert _safe_json_serialize_no_whitespaces(obj) == '<not serializable>'

0 commit comments

Comments
 (0)