google-adk 1.5.0__py3-none-any.whl → 1.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google/adk/a2a/converters/event_converter.py +257 -36
- google/adk/a2a/converters/part_converter.py +93 -25
- google/adk/a2a/converters/request_converter.py +12 -32
- google/adk/a2a/converters/utils.py +22 -4
- google/adk/a2a/executor/__init__.py +13 -0
- google/adk/a2a/executor/a2a_agent_executor.py +260 -0
- google/adk/a2a/executor/task_result_aggregator.py +71 -0
- google/adk/a2a/logs/__init__.py +13 -0
- google/adk/a2a/logs/log_utils.py +349 -0
- google/adk/agents/base_agent.py +54 -0
- google/adk/agents/llm_agent.py +15 -0
- google/adk/agents/remote_a2a_agent.py +532 -0
- google/adk/artifacts/in_memory_artifact_service.py +6 -3
- google/adk/cli/browser/chunk-EQDQRRRY.js +1 -0
- google/adk/cli/browser/chunk-TXJFAAIW.js +2 -0
- google/adk/cli/browser/index.html +4 -3
- google/adk/cli/browser/main-RXDVX3K6.js +3914 -0
- google/adk/cli/browser/polyfills-FFHMD2TL.js +17 -0
- google/adk/cli/cli_deploy.py +4 -1
- google/adk/cli/cli_eval.py +8 -6
- google/adk/cli/cli_tools_click.py +30 -10
- google/adk/cli/fast_api.py +120 -5
- google/adk/cli/utils/agent_loader.py +12 -0
- google/adk/evaluation/agent_evaluator.py +107 -10
- google/adk/evaluation/base_eval_service.py +157 -0
- google/adk/evaluation/constants.py +20 -0
- google/adk/evaluation/eval_case.py +3 -3
- google/adk/evaluation/eval_metrics.py +39 -0
- google/adk/evaluation/evaluation_generator.py +1 -1
- google/adk/evaluation/final_response_match_v2.py +230 -0
- google/adk/evaluation/llm_as_judge.py +141 -0
- google/adk/evaluation/llm_as_judge_utils.py +48 -0
- google/adk/evaluation/metric_evaluator_registry.py +89 -0
- google/adk/evaluation/response_evaluator.py +38 -211
- google/adk/evaluation/safety_evaluator.py +54 -0
- google/adk/evaluation/trajectory_evaluator.py +16 -2
- google/adk/evaluation/vertex_ai_eval_facade.py +147 -0
- google/adk/events/event.py +2 -4
- google/adk/flows/llm_flows/base_llm_flow.py +2 -0
- google/adk/memory/in_memory_memory_service.py +3 -2
- google/adk/models/lite_llm.py +50 -10
- google/adk/runners.py +27 -10
- google/adk/sessions/database_session_service.py +25 -7
- google/adk/sessions/in_memory_session_service.py +5 -1
- google/adk/sessions/vertex_ai_session_service.py +67 -42
- google/adk/tools/bigquery/config.py +11 -1
- google/adk/tools/bigquery/query_tool.py +306 -12
- google/adk/tools/enterprise_search_tool.py +2 -2
- google/adk/tools/function_tool.py +7 -1
- google/adk/tools/google_search_tool.py +1 -1
- google/adk/tools/mcp_tool/mcp_session_manager.py +44 -30
- google/adk/tools/mcp_tool/mcp_tool.py +44 -7
- google/adk/version.py +1 -1
- {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/METADATA +6 -4
- {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/RECORD +58 -42
- google/adk/cli/browser/main-JAAWEV7F.js +0 -92
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +0 -17
- {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/WHEEL +0 -0
- {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/entry_points.txt +0 -0
- {google_adk-1.5.0.dist-info → google_adk-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,8 @@
|
|
14
14
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
|
-
import datetime
|
17
|
+
from datetime import datetime
|
18
|
+
from datetime import timezone
|
18
19
|
import logging
|
19
20
|
from typing import Any
|
20
21
|
from typing import Dict
|
@@ -26,18 +27,24 @@ from a2a.server.events import Event as A2AEvent
|
|
26
27
|
from a2a.types import Artifact
|
27
28
|
from a2a.types import DataPart
|
28
29
|
from a2a.types import Message
|
30
|
+
from a2a.types import Part as A2APart
|
29
31
|
from a2a.types import Role
|
32
|
+
from a2a.types import Task
|
30
33
|
from a2a.types import TaskArtifactUpdateEvent
|
31
34
|
from a2a.types import TaskState
|
32
35
|
from a2a.types import TaskStatus
|
33
36
|
from a2a.types import TaskStatusUpdateEvent
|
34
37
|
from a2a.types import TextPart
|
38
|
+
from google.genai import types as genai_types
|
35
39
|
|
36
40
|
from ...agents.invocation_context import InvocationContext
|
37
41
|
from ...events.event import Event
|
38
|
-
from ...
|
42
|
+
from ...flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME
|
43
|
+
from ...utils.feature_decorator import experimental
|
44
|
+
from .part_converter import A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY
|
39
45
|
from .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
40
46
|
from .part_converter import A2A_DATA_PART_METADATA_TYPE_KEY
|
47
|
+
from .part_converter import convert_a2a_part_to_genai_part
|
41
48
|
from .part_converter import convert_genai_part_to_a2a_part
|
42
49
|
from .utils import _get_adk_metadata_key
|
43
50
|
|
@@ -143,6 +150,8 @@ def _convert_artifact_to_a2a_events(
|
|
143
150
|
invocation_context: InvocationContext,
|
144
151
|
filename: str,
|
145
152
|
version: int,
|
153
|
+
task_id: Optional[str] = None,
|
154
|
+
context_id: Optional[str] = None,
|
146
155
|
) -> TaskArtifactUpdateEvent:
|
147
156
|
"""Converts a new artifact version to an A2A TaskArtifactUpdateEvent.
|
148
157
|
|
@@ -151,6 +160,7 @@ def _convert_artifact_to_a2a_events(
|
|
151
160
|
invocation_context: The invocation context.
|
152
161
|
filename: The name of the artifact file.
|
153
162
|
version: The version number of the artifact.
|
163
|
+
task_id: Optional task ID to use for generated events. If not provided, new UUIDs will be generated.
|
154
164
|
|
155
165
|
Returns:
|
156
166
|
A TaskArtifactUpdateEvent representing the artifact update.
|
@@ -186,9 +196,9 @@ def _convert_artifact_to_a2a_events(
|
|
186
196
|
)
|
187
197
|
|
188
198
|
return TaskArtifactUpdateEvent(
|
189
|
-
taskId=
|
199
|
+
taskId=task_id,
|
190
200
|
append=False,
|
191
|
-
contextId=
|
201
|
+
contextId=context_id,
|
192
202
|
lastChunk=True,
|
193
203
|
artifact=Artifact(
|
194
204
|
artifactId=artifact_id,
|
@@ -210,7 +220,7 @@ def _convert_artifact_to_a2a_events(
|
|
210
220
|
raise RuntimeError(f"Artifact conversion failed: {e}") from e
|
211
221
|
|
212
222
|
|
213
|
-
def _process_long_running_tool(a2a_part, event: Event) -> None:
|
223
|
+
def _process_long_running_tool(a2a_part: A2APart, event: Event) -> None:
|
214
224
|
"""Processes long-running tool metadata for an A2A part.
|
215
225
|
|
216
226
|
Args:
|
@@ -220,18 +230,172 @@ def _process_long_running_tool(a2a_part, event: Event) -> None:
|
|
220
230
|
if (
|
221
231
|
isinstance(a2a_part.root, DataPart)
|
222
232
|
and event.long_running_tool_ids
|
233
|
+
and a2a_part.root.metadata
|
223
234
|
and a2a_part.root.metadata.get(
|
224
235
|
_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
|
225
236
|
)
|
226
237
|
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
227
|
-
and a2a_part.root.
|
238
|
+
and a2a_part.root.data.get("id") in event.long_running_tool_ids
|
228
239
|
):
|
229
|
-
a2a_part.root.metadata[
|
240
|
+
a2a_part.root.metadata[
|
241
|
+
_get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
|
242
|
+
] = True
|
230
243
|
|
231
244
|
|
232
|
-
|
233
|
-
|
234
|
-
|
245
|
+
def convert_a2a_task_to_event(
|
246
|
+
a2a_task: Task,
|
247
|
+
author: Optional[str] = None,
|
248
|
+
invocation_context: Optional[InvocationContext] = None,
|
249
|
+
) -> Event:
|
250
|
+
"""Converts an A2A task to an ADK event.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
a2a_task: The A2A task to convert. Must not be None.
|
254
|
+
author: The author of the event. Defaults to "a2a agent" if not provided.
|
255
|
+
invocation_context: The invocation context containing session information.
|
256
|
+
If provided, the branch will be set from the context.
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
An ADK Event object representing the converted task.
|
260
|
+
|
261
|
+
Raises:
|
262
|
+
ValueError: If a2a_task is None.
|
263
|
+
RuntimeError: If conversion of the underlying message fails.
|
264
|
+
"""
|
265
|
+
if a2a_task is None:
|
266
|
+
raise ValueError("A2A task cannot be None")
|
267
|
+
|
268
|
+
try:
|
269
|
+
# Extract message from task status or history
|
270
|
+
message = None
|
271
|
+
if a2a_task.status and a2a_task.status.message:
|
272
|
+
message = a2a_task.status.message
|
273
|
+
elif a2a_task.history:
|
274
|
+
message = a2a_task.history[-1]
|
275
|
+
|
276
|
+
# Convert message if available
|
277
|
+
if message:
|
278
|
+
try:
|
279
|
+
return convert_a2a_message_to_event(message, author, invocation_context)
|
280
|
+
except Exception as e:
|
281
|
+
logger.error("Failed to convert A2A task message to event: %s", e)
|
282
|
+
raise RuntimeError(f"Failed to convert task message: {e}") from e
|
283
|
+
|
284
|
+
# Create minimal event if no message is available
|
285
|
+
return Event(
|
286
|
+
invocation_id=(
|
287
|
+
invocation_context.invocation_id
|
288
|
+
if invocation_context
|
289
|
+
else str(uuid.uuid4())
|
290
|
+
),
|
291
|
+
author=author or "a2a agent",
|
292
|
+
branch=invocation_context.branch if invocation_context else None,
|
293
|
+
)
|
294
|
+
|
295
|
+
except Exception as e:
|
296
|
+
logger.error("Failed to convert A2A task to event: %s", e)
|
297
|
+
raise
|
298
|
+
|
299
|
+
|
300
|
+
@experimental
|
301
|
+
def convert_a2a_message_to_event(
|
302
|
+
a2a_message: Message,
|
303
|
+
author: Optional[str] = None,
|
304
|
+
invocation_context: Optional[InvocationContext] = None,
|
305
|
+
) -> Event:
|
306
|
+
"""Converts an A2A message to an ADK event.
|
307
|
+
|
308
|
+
Args:
|
309
|
+
a2a_message: The A2A message to convert. Must not be None.
|
310
|
+
author: The author of the event. Defaults to "a2a agent" if not provided.
|
311
|
+
invocation_context: The invocation context containing session information.
|
312
|
+
If provided, the branch will be set from the context.
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
An ADK Event object with converted content and long-running tool metadata.
|
316
|
+
|
317
|
+
Raises:
|
318
|
+
ValueError: If a2a_message is None.
|
319
|
+
RuntimeError: If conversion of message parts fails.
|
320
|
+
"""
|
321
|
+
if a2a_message is None:
|
322
|
+
raise ValueError("A2A message cannot be None")
|
323
|
+
|
324
|
+
if not a2a_message.parts:
|
325
|
+
logger.warning(
|
326
|
+
"A2A message has no parts, creating event with empty content"
|
327
|
+
)
|
328
|
+
return Event(
|
329
|
+
invocation_id=(
|
330
|
+
invocation_context.invocation_id
|
331
|
+
if invocation_context
|
332
|
+
else str(uuid.uuid4())
|
333
|
+
),
|
334
|
+
author=author or "a2a agent",
|
335
|
+
branch=invocation_context.branch if invocation_context else None,
|
336
|
+
content=genai_types.Content(role="model", parts=[]),
|
337
|
+
)
|
338
|
+
|
339
|
+
try:
|
340
|
+
parts = []
|
341
|
+
long_running_tool_ids = set()
|
342
|
+
|
343
|
+
for a2a_part in a2a_message.parts:
|
344
|
+
try:
|
345
|
+
part = convert_a2a_part_to_genai_part(a2a_part)
|
346
|
+
if part is None:
|
347
|
+
logger.warning("Failed to convert A2A part, skipping: %s", a2a_part)
|
348
|
+
continue
|
349
|
+
|
350
|
+
# Check for long-running tools
|
351
|
+
if (
|
352
|
+
a2a_part.root.metadata
|
353
|
+
and a2a_part.root.metadata.get(
|
354
|
+
_get_adk_metadata_key(
|
355
|
+
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY
|
356
|
+
)
|
357
|
+
)
|
358
|
+
is True
|
359
|
+
):
|
360
|
+
long_running_tool_ids.add(part.function_call.id)
|
361
|
+
|
362
|
+
parts.append(part)
|
363
|
+
|
364
|
+
except Exception as e:
|
365
|
+
logger.error("Failed to convert A2A part: %s, error: %s", a2a_part, e)
|
366
|
+
# Continue processing other parts instead of failing completely
|
367
|
+
continue
|
368
|
+
|
369
|
+
if not parts:
|
370
|
+
logger.warning(
|
371
|
+
"No parts could be converted from A2A message %s", a2a_message
|
372
|
+
)
|
373
|
+
|
374
|
+
return Event(
|
375
|
+
invocation_id=(
|
376
|
+
invocation_context.invocation_id
|
377
|
+
if invocation_context
|
378
|
+
else str(uuid.uuid4())
|
379
|
+
),
|
380
|
+
author=author or "a2a agent",
|
381
|
+
branch=invocation_context.branch if invocation_context else None,
|
382
|
+
long_running_tool_ids=long_running_tool_ids
|
383
|
+
if long_running_tool_ids
|
384
|
+
else None,
|
385
|
+
content=genai_types.Content(
|
386
|
+
role="model",
|
387
|
+
parts=parts,
|
388
|
+
),
|
389
|
+
)
|
390
|
+
|
391
|
+
except Exception as e:
|
392
|
+
logger.error("Failed to convert A2A message to event: %s", e)
|
393
|
+
raise RuntimeError(f"Failed to convert message: {e}") from e
|
394
|
+
|
395
|
+
|
396
|
+
@experimental
|
397
|
+
def convert_event_to_a2a_message(
|
398
|
+
event: Event, invocation_context: InvocationContext, role: Role = Role.agent
|
235
399
|
) -> Optional[Message]:
|
236
400
|
"""Converts an ADK event to an A2A message.
|
237
401
|
|
@@ -262,9 +426,7 @@ def convert_event_to_a2a_status_message(
|
|
262
426
|
_process_long_running_tool(a2a_part, event)
|
263
427
|
|
264
428
|
if a2a_parts:
|
265
|
-
return Message(
|
266
|
-
messageId=str(uuid.uuid4()), role=Role.agent, parts=a2a_parts
|
267
|
-
)
|
429
|
+
return Message(messageId=str(uuid.uuid4()), role=role, parts=a2a_parts)
|
268
430
|
|
269
431
|
except Exception as e:
|
270
432
|
logger.error("Failed to convert event to status message: %s", e)
|
@@ -274,38 +436,57 @@ def convert_event_to_a2a_status_message(
|
|
274
436
|
|
275
437
|
|
276
438
|
def _create_error_status_event(
|
277
|
-
event: Event,
|
439
|
+
event: Event,
|
440
|
+
invocation_context: InvocationContext,
|
441
|
+
task_id: Optional[str] = None,
|
442
|
+
context_id: Optional[str] = None,
|
278
443
|
) -> TaskStatusUpdateEvent:
|
279
444
|
"""Creates a TaskStatusUpdateEvent for error scenarios.
|
280
445
|
|
281
446
|
Args:
|
282
447
|
event: The ADK event containing error information.
|
283
448
|
invocation_context: The invocation context.
|
449
|
+
task_id: Optional task ID to use for generated events.
|
450
|
+
context_id: Optional Context ID to use for generated events.
|
284
451
|
|
285
452
|
Returns:
|
286
453
|
A TaskStatusUpdateEvent with FAILED state.
|
287
454
|
"""
|
288
455
|
error_message = getattr(event, "error_message", None) or DEFAULT_ERROR_MESSAGE
|
289
456
|
|
457
|
+
# Get context metadata and add error code
|
458
|
+
event_metadata = _get_context_metadata(event, invocation_context)
|
459
|
+
if event.error_code:
|
460
|
+
event_metadata[_get_adk_metadata_key("error_code")] = str(event.error_code)
|
461
|
+
|
290
462
|
return TaskStatusUpdateEvent(
|
291
|
-
taskId=
|
292
|
-
contextId=
|
293
|
-
|
294
|
-
metadata=_get_context_metadata(event, invocation_context),
|
463
|
+
taskId=task_id,
|
464
|
+
contextId=context_id,
|
465
|
+
metadata=event_metadata,
|
295
466
|
status=TaskStatus(
|
296
467
|
state=TaskState.failed,
|
297
468
|
message=Message(
|
298
469
|
messageId=str(uuid.uuid4()),
|
299
470
|
role=Role.agent,
|
300
471
|
parts=[TextPart(text=error_message)],
|
472
|
+
metadata={
|
473
|
+
_get_adk_metadata_key("error_code"): str(event.error_code)
|
474
|
+
}
|
475
|
+
if event.error_code
|
476
|
+
else {},
|
301
477
|
),
|
302
|
-
timestamp=datetime.
|
478
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
303
479
|
),
|
480
|
+
final=False,
|
304
481
|
)
|
305
482
|
|
306
483
|
|
307
|
-
def
|
308
|
-
message: Message,
|
484
|
+
def _create_status_update_event(
|
485
|
+
message: Message,
|
486
|
+
invocation_context: InvocationContext,
|
487
|
+
event: Event,
|
488
|
+
task_id: Optional[str] = None,
|
489
|
+
context_id: Optional[str] = None,
|
309
490
|
) -> TaskStatusUpdateEvent:
|
310
491
|
"""Creates a TaskStatusUpdateEvent for running scenarios.
|
311
492
|
|
@@ -313,32 +494,70 @@ def _create_running_status_event(
|
|
313
494
|
message: The A2A message to include.
|
314
495
|
invocation_context: The invocation context.
|
315
496
|
event: The ADK event.
|
497
|
+
task_id: Optional task ID to use for generated events.
|
498
|
+
context_id: Optional Context ID to use for generated events.
|
499
|
+
|
316
500
|
|
317
501
|
Returns:
|
318
502
|
A TaskStatusUpdateEvent with RUNNING state.
|
319
503
|
"""
|
504
|
+
status = TaskStatus(
|
505
|
+
state=TaskState.working,
|
506
|
+
message=message,
|
507
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
508
|
+
)
|
509
|
+
|
510
|
+
if any(
|
511
|
+
part.root.metadata.get(
|
512
|
+
_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
|
513
|
+
)
|
514
|
+
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
515
|
+
and part.root.metadata.get(
|
516
|
+
_get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
|
517
|
+
)
|
518
|
+
is True
|
519
|
+
and part.root.data.get("name") == REQUEST_EUC_FUNCTION_CALL_NAME
|
520
|
+
for part in message.parts
|
521
|
+
if part.root.metadata
|
522
|
+
):
|
523
|
+
status.state = TaskState.auth_required
|
524
|
+
elif any(
|
525
|
+
part.root.metadata.get(
|
526
|
+
_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
|
527
|
+
)
|
528
|
+
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
529
|
+
and part.root.metadata.get(
|
530
|
+
_get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)
|
531
|
+
)
|
532
|
+
is True
|
533
|
+
for part in message.parts
|
534
|
+
if part.root.metadata
|
535
|
+
):
|
536
|
+
status.state = TaskState.input_required
|
537
|
+
|
320
538
|
return TaskStatusUpdateEvent(
|
321
|
-
taskId=
|
322
|
-
contextId=
|
323
|
-
|
324
|
-
status=TaskStatus(
|
325
|
-
state=TaskState.working,
|
326
|
-
message=message,
|
327
|
-
timestamp=datetime.datetime.now().isoformat(),
|
328
|
-
),
|
539
|
+
taskId=task_id,
|
540
|
+
contextId=context_id,
|
541
|
+
status=status,
|
329
542
|
metadata=_get_context_metadata(event, invocation_context),
|
543
|
+
final=False,
|
330
544
|
)
|
331
545
|
|
332
546
|
|
333
|
-
@
|
547
|
+
@experimental
|
334
548
|
def convert_event_to_a2a_events(
|
335
|
-
event: Event,
|
549
|
+
event: Event,
|
550
|
+
invocation_context: InvocationContext,
|
551
|
+
task_id: Optional[str] = None,
|
552
|
+
context_id: Optional[str] = None,
|
336
553
|
) -> List[A2AEvent]:
|
337
554
|
"""Converts a GenAI event to a list of A2A events.
|
338
555
|
|
339
556
|
Args:
|
340
557
|
event: The ADK event to convert.
|
341
558
|
invocation_context: The invocation context.
|
559
|
+
task_id: Optional task ID to use for generated events.
|
560
|
+
context_id: Optional Context ID to use for generated events.
|
342
561
|
|
343
562
|
Returns:
|
344
563
|
A list of A2A events representing the converted ADK event.
|
@@ -358,20 +577,22 @@ def convert_event_to_a2a_events(
|
|
358
577
|
if event.actions.artifact_delta:
|
359
578
|
for filename, version in event.actions.artifact_delta.items():
|
360
579
|
artifact_event = _convert_artifact_to_a2a_events(
|
361
|
-
event, invocation_context, filename, version
|
580
|
+
event, invocation_context, filename, version, task_id, context_id
|
362
581
|
)
|
363
582
|
a2a_events.append(artifact_event)
|
364
583
|
|
365
584
|
# Handle error scenarios
|
366
585
|
if event.error_code:
|
367
|
-
error_event = _create_error_status_event(
|
586
|
+
error_event = _create_error_status_event(
|
587
|
+
event, invocation_context, task_id, context_id
|
588
|
+
)
|
368
589
|
a2a_events.append(error_event)
|
369
590
|
|
370
591
|
# Handle regular message content
|
371
|
-
message =
|
592
|
+
message = convert_event_to_a2a_message(event, invocation_context)
|
372
593
|
if message:
|
373
|
-
running_event =
|
374
|
-
message, invocation_context, event
|
594
|
+
running_event = _create_status_update_event(
|
595
|
+
message, invocation_context, event, task_id, context_id
|
375
596
|
)
|
376
597
|
a2a_events.append(running_event)
|
377
598
|
|
@@ -18,9 +18,9 @@ module containing utilities for conversion betwen A2A Part and Google GenAI Part
|
|
18
18
|
|
19
19
|
from __future__ import annotations
|
20
20
|
|
21
|
+
import base64
|
21
22
|
import json
|
22
23
|
import logging
|
23
|
-
import sys
|
24
24
|
from typing import Optional
|
25
25
|
|
26
26
|
from .utils import _get_adk_metadata_key
|
@@ -28,26 +28,30 @@ from .utils import _get_adk_metadata_key
|
|
28
28
|
try:
|
29
29
|
from a2a import types as a2a_types
|
30
30
|
except ImportError as e:
|
31
|
+
import sys
|
32
|
+
|
31
33
|
if sys.version_info < (3, 10):
|
32
34
|
raise ImportError(
|
33
|
-
'A2A
|
34
|
-
' version.'
|
35
|
+
'A2A requires Python 3.10 or above. Please upgrade your Python version.'
|
35
36
|
) from e
|
36
37
|
else:
|
37
38
|
raise e
|
38
39
|
|
39
40
|
from google.genai import types as genai_types
|
40
41
|
|
41
|
-
from ...utils.feature_decorator import
|
42
|
+
from ...utils.feature_decorator import experimental
|
42
43
|
|
43
44
|
logger = logging.getLogger('google_adk.' + __name__)
|
44
45
|
|
45
46
|
A2A_DATA_PART_METADATA_TYPE_KEY = 'type'
|
47
|
+
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = 'is_long_running'
|
46
48
|
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'
|
47
49
|
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'
|
50
|
+
A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'
|
51
|
+
A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'
|
48
52
|
|
49
53
|
|
50
|
-
@
|
54
|
+
@experimental
|
51
55
|
def convert_a2a_part_to_genai_part(
|
52
56
|
a2a_part: a2a_types.Part,
|
53
57
|
) -> Optional[genai_types.Part]:
|
@@ -67,7 +71,8 @@ def convert_a2a_part_to_genai_part(
|
|
67
71
|
elif isinstance(part.file, a2a_types.FileWithBytes):
|
68
72
|
return genai_types.Part(
|
69
73
|
inline_data=genai_types.Blob(
|
70
|
-
data=part.file.bytes
|
74
|
+
data=base64.b64decode(part.file.bytes),
|
75
|
+
mime_type=part.file.mimeType,
|
71
76
|
)
|
72
77
|
)
|
73
78
|
else:
|
@@ -84,7 +89,11 @@ def convert_a2a_part_to_genai_part(
|
|
84
89
|
# response.
|
85
90
|
# TODO once A2A defined how to suervice such information, migrate below
|
86
91
|
# logic accordinlgy
|
87
|
-
if
|
92
|
+
if (
|
93
|
+
part.metadata
|
94
|
+
and _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
|
95
|
+
in part.metadata
|
96
|
+
):
|
88
97
|
if (
|
89
98
|
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
|
90
99
|
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
@@ -103,6 +112,24 @@ def convert_a2a_part_to_genai_part(
|
|
103
112
|
part.data, by_alias=True
|
104
113
|
)
|
105
114
|
)
|
115
|
+
if (
|
116
|
+
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
|
117
|
+
== A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
|
118
|
+
):
|
119
|
+
return genai_types.Part(
|
120
|
+
code_execution_result=genai_types.CodeExecutionResult.model_validate(
|
121
|
+
part.data, by_alias=True
|
122
|
+
)
|
123
|
+
)
|
124
|
+
if (
|
125
|
+
part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
|
126
|
+
== A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
|
127
|
+
):
|
128
|
+
return genai_types.Part(
|
129
|
+
executable_code=genai_types.ExecutableCode.model_validate(
|
130
|
+
part.data, by_alias=True
|
131
|
+
)
|
132
|
+
)
|
106
133
|
return genai_types.Part(text=json.dumps(part.data))
|
107
134
|
|
108
135
|
logger.warning(
|
@@ -113,32 +140,45 @@ def convert_a2a_part_to_genai_part(
|
|
113
140
|
return None
|
114
141
|
|
115
142
|
|
116
|
-
@
|
143
|
+
@experimental
|
117
144
|
def convert_genai_part_to_a2a_part(
|
118
145
|
part: genai_types.Part,
|
119
146
|
) -> Optional[a2a_types.Part]:
|
120
147
|
"""Convert a Google GenAI Part to an A2A Part."""
|
148
|
+
|
121
149
|
if part.text:
|
122
|
-
|
150
|
+
a2a_part = a2a_types.TextPart(text=part.text)
|
151
|
+
if part.thought is not None:
|
152
|
+
a2a_part.metadata = {_get_adk_metadata_key('thought'): part.thought}
|
153
|
+
return a2a_types.Part(root=a2a_part)
|
123
154
|
|
124
155
|
if part.file_data:
|
125
|
-
return a2a_types.
|
126
|
-
|
127
|
-
|
128
|
-
|
156
|
+
return a2a_types.Part(
|
157
|
+
root=a2a_types.FilePart(
|
158
|
+
file=a2a_types.FileWithUri(
|
159
|
+
uri=part.file_data.file_uri,
|
160
|
+
mimeType=part.file_data.mime_type,
|
161
|
+
)
|
129
162
|
)
|
130
163
|
)
|
131
164
|
|
132
165
|
if part.inline_data:
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
mimeType=part.inline_data.mime_type,
|
138
|
-
)
|
166
|
+
a2a_part = a2a_types.FilePart(
|
167
|
+
file=a2a_types.FileWithBytes(
|
168
|
+
bytes=base64.b64encode(part.inline_data.data).decode('utf-8'),
|
169
|
+
mimeType=part.inline_data.mime_type,
|
139
170
|
)
|
140
171
|
)
|
141
172
|
|
173
|
+
if part.video_metadata:
|
174
|
+
a2a_part.metadata = {
|
175
|
+
_get_adk_metadata_key(
|
176
|
+
'video_metadata'
|
177
|
+
): part.video_metadata.model_dump(by_alias=True, exclude_none=True)
|
178
|
+
}
|
179
|
+
|
180
|
+
return a2a_types.Part(root=a2a_part)
|
181
|
+
|
142
182
|
# Conver the funcall and function reponse to A2A DataPart.
|
143
183
|
# This is mainly for converting human in the loop and auth request and
|
144
184
|
# response.
|
@@ -151,9 +191,9 @@ def convert_genai_part_to_a2a_part(
|
|
151
191
|
by_alias=True, exclude_none=True
|
152
192
|
),
|
153
193
|
metadata={
|
154
|
-
|
155
|
-
|
156
|
-
)
|
194
|
+
_get_adk_metadata_key(
|
195
|
+
A2A_DATA_PART_METADATA_TYPE_KEY
|
196
|
+
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
157
197
|
},
|
158
198
|
)
|
159
199
|
)
|
@@ -165,9 +205,37 @@ def convert_genai_part_to_a2a_part(
|
|
165
205
|
by_alias=True, exclude_none=True
|
166
206
|
),
|
167
207
|
metadata={
|
168
|
-
|
169
|
-
|
170
|
-
)
|
208
|
+
_get_adk_metadata_key(
|
209
|
+
A2A_DATA_PART_METADATA_TYPE_KEY
|
210
|
+
): A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
|
211
|
+
},
|
212
|
+
)
|
213
|
+
)
|
214
|
+
|
215
|
+
if part.code_execution_result:
|
216
|
+
return a2a_types.Part(
|
217
|
+
root=a2a_types.DataPart(
|
218
|
+
data=part.code_execution_result.model_dump(
|
219
|
+
by_alias=True, exclude_none=True
|
220
|
+
),
|
221
|
+
metadata={
|
222
|
+
_get_adk_metadata_key(
|
223
|
+
A2A_DATA_PART_METADATA_TYPE_KEY
|
224
|
+
): A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT
|
225
|
+
},
|
226
|
+
)
|
227
|
+
)
|
228
|
+
|
229
|
+
if part.executable_code:
|
230
|
+
return a2a_types.Part(
|
231
|
+
root=a2a_types.DataPart(
|
232
|
+
data=part.executable_code.model_dump(
|
233
|
+
by_alias=True, exclude_none=True
|
234
|
+
),
|
235
|
+
metadata={
|
236
|
+
_get_adk_metadata_key(
|
237
|
+
A2A_DATA_PART_METADATA_TYPE_KEY
|
238
|
+
): A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE
|
171
239
|
},
|
172
240
|
)
|
173
241
|
)
|