Skip to content

Commit 0c1f1fa

Browse files
google-genai-botcopybara-github
authored andcommitted
fix: Fix logging issues with RemoteA2aAgent
Recent change to the updated A2A Client SDK broke the logging utilities. This updates those logging utilities to work with the new A2A SDK structure. PiperOrigin-RevId: 806482017
1 parent bb14800 commit 0c1f1fa

File tree

3 files changed

+79
-209
lines changed

3 files changed

+79
-209
lines changed

src/google/adk/a2a/logs/log_utils.py

Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@
2020
import sys
2121

2222
try:
23+
from a2a.client import ClientEvent as A2AClientEvent
2324
from a2a.types import DataPart as A2ADataPart
2425
from a2a.types import Message as A2AMessage
2526
from a2a.types import Part as A2APart
26-
from a2a.types import SendMessageRequest
27-
from a2a.types import SendMessageResponse
2827
from a2a.types import Task as A2ATask
2928
from a2a.types import TextPart as A2ATextPart
3029
except ImportError as e:
@@ -49,6 +48,16 @@ def _is_a2a_task(obj) -> bool:
4948
return type(obj).__name__ == "Task" and hasattr(obj, "status")
5049

5150

51+
def _is_a2a_client_event(obj) -> bool:
52+
"""Check if an object is an A2A Client Event (Task, UpdateEvent) tuple."""
53+
try:
54+
return isinstance(obj, tuple) and _is_a2a_task(obj[0])
55+
except (TypeError, AttributeError):
56+
return (
57+
hasattr(obj, "__getitem__") and len(obj) == 2 and _is_a2a_task(obj[0])
58+
)
59+
60+
5261
def _is_a2a_message(obj) -> bool:
5362
"""Check if an object is an A2A Message, with fallback for isinstance issues."""
5463
try:
@@ -114,7 +123,7 @@ def build_message_part_log(part: A2APart) -> str:
114123
return part_content
115124

116125

117-
def build_a2a_request_log(req: SendMessageRequest) -> str:
126+
def build_a2a_request_log(req: A2AMessage) -> str:
118127
"""Builds a structured log representation of an A2A request.
119128
120129
Args:
@@ -125,100 +134,70 @@ def build_a2a_request_log(req: SendMessageRequest) -> str:
125134
"""
126135
# Message parts logs
127136
message_parts_logs = []
128-
if req.params.message.parts:
129-
for i, part in enumerate(req.params.message.parts):
137+
if req.parts:
138+
for i, part in enumerate(req.parts):
130139
part_log = build_message_part_log(part)
131140
# Replace any internal newlines with indented newlines to maintain formatting
132141
part_log_formatted = part_log.replace("\n", "\n ")
133142
message_parts_logs.append(f"Part {i}: {part_log_formatted}")
134143

135-
# Configuration logs
136-
config_log = "None"
137-
if req.params.configuration:
138-
config_data = {
139-
"accepted_output_modes": req.params.configuration.accepted_output_modes,
140-
"blocking": req.params.configuration.blocking,
141-
"history_length": req.params.configuration.history_length,
142-
"push_notification_config": bool(
143-
req.params.configuration.push_notification_config
144-
),
145-
}
146-
config_log = json.dumps(config_data, indent=2)
147-
148144
# Build message metadata section
149145
message_metadata_section = ""
150-
if req.params.message.metadata:
146+
if req.metadata:
151147
message_metadata_section = f"""
152148
Metadata:
153-
{json.dumps(req.params.message.metadata, indent=2).replace(chr(10), chr(10) + ' ')}"""
149+
{json.dumps(req.metadata, indent=2).replace(chr(10), chr(10) + ' ')}"""
154150

155151
# Build optional sections
156152
optional_sections = []
157153

158-
if req.params.metadata:
154+
if req.metadata:
159155
optional_sections.append(
160156
f"""-----------------------------------------------------------
161157
Metadata:
162-
{json.dumps(req.params.metadata, indent=2)}"""
158+
{json.dumps(req.metadata, indent=2)}"""
163159
)
164160

165161
optional_sections_str = _NEW_LINE.join(optional_sections)
166162

167163
return f"""
168-
A2A Request:
169-
-----------------------------------------------------------
170-
Request ID: {req.id}
171-
Method: {req.method}
172-
JSON-RPC: {req.jsonrpc}
164+
A2A Send Message Request:
173165
-----------------------------------------------------------
174166
Message:
175-
ID: {req.params.message.message_id}
176-
Role: {req.params.message.role}
177-
Task ID: {req.params.message.task_id}
178-
Context ID: {req.params.message.context_id}{message_metadata_section}
167+
ID: {req.message_id}
168+
Role: {req.role}
169+
Task ID: {req.task_id}
170+
Context ID: {req.context_id}{message_metadata_section}
179171
-----------------------------------------------------------
180172
Message Parts:
181173
{_NEW_LINE.join(message_parts_logs) if message_parts_logs else "No parts"}
182174
-----------------------------------------------------------
183-
Configuration:
184-
{config_log}
185175
{optional_sections_str}
186176
-----------------------------------------------------------
187177
"""
188178

189179

190-
def build_a2a_response_log(resp: SendMessageResponse) -> str:
180+
def build_a2a_response_log(resp: A2AClientEvent | A2AMessage) -> str:
191181
"""Builds a structured log representation of an A2A response.
192182
193183
Args:
194-
resp: The A2A SendMessageResponse to log.
184+
resp: The A2A SendMessage Response to log.
195185
196186
Returns:
197187
A formatted string representation of the response.
198188
"""
199-
# Handle error responses
200-
if hasattr(resp.root, "error"):
201-
return f"""
202-
A2A Response:
203-
-----------------------------------------------------------
204-
Type: ERROR
205-
Error Code: {resp.root.error.code}
206-
Error Message: {resp.root.error.message}
207-
Error Data: {json.dumps(resp.root.error.data, indent=2) if resp.root.error.data else "None"}
208-
-----------------------------------------------------------
209-
Response ID: {resp.root.id}
210-
JSON-RPC: {resp.root.jsonrpc}
211-
-----------------------------------------------------------
212-
"""
213189

214190
# Handle success responses
215-
result = resp.root.result
191+
result = resp
216192
result_type = type(result).__name__
193+
if result_type == "tuple":
194+
result_type = "ClientEvent"
217195

218196
# Build result details based on type
219197
result_details = []
220198

221-
if _is_a2a_task(result):
199+
if _is_a2a_client_event(result):
200+
result = result[0]
222201
result_details.extend([
223202
f"Task ID: {result.id}",
224203
f"Context ID: {result.context_id}",
@@ -342,7 +321,4 @@ def build_a2a_response_log(resp: SendMessageResponse) -> str:
342321
History:
343322
{history_section}
344323
-----------------------------------------------------------
345-
Response ID: {resp.root.id}
346-
JSON-RPC: {resp.root.jsonrpc}
347-
-----------------------------------------------------------
348324
"""

src/google/adk/agents/remote_a2a_agent.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -492,9 +492,16 @@ async def _run_async_impl(
492492
event.custom_metadata[A2A_METADATA_PREFIX + "request"] = (
493493
a2a_request.model_dump(exclude_none=True, by_alias=True)
494494
)
495-
event.custom_metadata[A2A_METADATA_PREFIX + "response"] = (
496-
a2a_response.model_dump(exclude_none=True, by_alias=True)
497-
)
495+
# If the response is a ClientEvent, record the task state, otherwise
496+
# record the message object.
497+
if isinstance(a2a_response, tuple):
498+
event.custom_metadata[A2A_METADATA_PREFIX + "response"] = (
499+
a2a_response[0].model_dump(exclude_none=True, by_alias=True)
500+
)
501+
else:
502+
event.custom_metadata[A2A_METADATA_PREFIX + "response"] = (
503+
a2a_response.model_dump(exclude_none=True, by_alias=True)
504+
)
498505

499506
yield event
500507

0 commit comments

Comments
 (0)