From 97d6dbfb1a39895189d48dd8aa02c208f82ef143 Mon Sep 17 00:00:00 2001 From: Nilesh Patil <128893479+nileshpatil6@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:48:08 +0530 Subject: [PATCH 1/2] fix(tracing): catch ValueError and RecursionError in _safe_json_serialize --- src/google/adk/telemetry/tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index 55c37a6c60..4ed40d2585 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -128,7 +128,7 @@ def _safe_json_serialize(obj) -> str: return json.dumps( obj, ensure_ascii=False, default=lambda o: '' ) - except (TypeError, OverflowError): + except (TypeError, OverflowError, ValueError, RecursionError): return '' From 7751ea5346b02fcfa22ff9ad95cc2d6d3a98cfff Mon Sep 17 00:00:00 2001 From: Nilesh Patil <128893479+nileshpatil6@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:53:48 +0530 Subject: [PATCH 2/2] fix(tracing): catch RecursionError in _safe_json_serialize Upstream already added ValueError; add RecursionError to guard against deeply nested structures exceeding Python's recursion limit when json.dumps is called inside telemetry finally blocks. Fixes #5411 --- src/google/adk/telemetry/tracing.py | 58 ++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index 4ed40d2585..ab395337cf 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -128,7 +128,7 @@ def _safe_json_serialize(obj) -> str: return json.dumps( obj, ensure_ascii=False, default=lambda o: '' ) - except (TypeError, OverflowError, ValueError, RecursionError): + except (TypeError, ValueError, OverflowError, RecursionError): return '' @@ -170,6 +170,7 @@ def trace_tool_call( args: dict[str, Any], function_response_event: Event | None, error: Exception | None = None, + span: Span | None = None, ): """Traces tool call. @@ -178,8 +179,9 @@ def trace_tool_call( args: The arguments to the tool call. function_response_event: The event with the function response details. error: The exception raised during tool execution, if any. + span: The span to record attributes on. If None, uses current span. """ - span = trace.get_current_span() + span = span or trace.get_current_span() span.set_attribute(GEN_AI_OPERATION_NAME, 'execute_tool') @@ -443,6 +445,58 @@ def trace_send_data( span.set_attribute('gcp.vertex.agent.data', '{}') +def _build_compaction_attributes( + *, + session_id: str, + trigger: str, + summarizer_type: str, + event_count: int, + token_threshold: int | None = None, + event_retention_size: int | None = None, + compaction_interval: int | None = None, + overlap_size: int | None = None, +) -> dict[str, AttributeValue]: + """Builds span attributes for event compaction tracing.""" + attributes: dict[str, AttributeValue] = { + GEN_AI_SYSTEM: _guess_gemini_system_name(), + GEN_AI_OPERATION_NAME: 'compact_events', + GEN_AI_CONVERSATION_ID: session_id, + 'gen_ai.compaction.trigger': trigger, + 'gen_ai.compaction.summarizer_type': summarizer_type, + 'gen_ai.compaction.event_count': event_count, + } + if token_threshold is not None: + attributes['gen_ai.compaction.token_threshold'] = token_threshold + if event_retention_size is not None: + attributes['gen_ai.compaction.event_retention_size'] = event_retention_size + if compaction_interval is not None: + attributes['gen_ai.compaction.compaction_interval'] = compaction_interval + if overlap_size is not None: + attributes['gen_ai.compaction.overlap_size'] = overlap_size + return attributes + + +def _build_compaction_result_attributes( + compacted_event: Event | None, +) -> dict[str, AttributeValue]: + """Builds span attributes for compaction result.""" + if ( + compacted_event is None + or compacted_event.actions is None + or compacted_event.actions.compaction is None + ): + return {} + + attributes: dict[str, AttributeValue] = {} + compaction = compacted_event.actions.compaction + attributes['gen_ai.compaction.result_event_id'] = compacted_event.id + if compaction.start_timestamp is not None: + attributes['gen_ai.compaction.start_timestamp'] = compaction.start_timestamp + if compaction.end_timestamp is not None: + attributes['gen_ai.compaction.end_timestamp'] = compaction.end_timestamp + return attributes + + def _build_llm_request_for_trace(llm_request: LlmRequest) -> dict[str, Any]: """Builds a dictionary representation of the LLM request for tracing.