google-adk 1.6.1__py3-none-any.whl → 1.8.0__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.
Files changed (110) hide show
  1. google/adk/a2a/converters/event_converter.py +5 -85
  2. google/adk/a2a/converters/request_converter.py +1 -2
  3. google/adk/a2a/executor/a2a_agent_executor.py +45 -16
  4. google/adk/a2a/logs/log_utils.py +1 -2
  5. google/adk/a2a/utils/__init__.py +0 -0
  6. google/adk/a2a/utils/agent_card_builder.py +544 -0
  7. google/adk/a2a/utils/agent_to_a2a.py +118 -0
  8. google/adk/agents/__init__.py +5 -0
  9. google/adk/agents/agent_config.py +46 -0
  10. google/adk/agents/base_agent.py +239 -41
  11. google/adk/agents/callback_context.py +41 -0
  12. google/adk/agents/common_configs.py +79 -0
  13. google/adk/agents/config_agent_utils.py +184 -0
  14. google/adk/agents/config_schemas/AgentConfig.json +566 -0
  15. google/adk/agents/invocation_context.py +5 -1
  16. google/adk/agents/live_request_queue.py +15 -0
  17. google/adk/agents/llm_agent.py +201 -9
  18. google/adk/agents/loop_agent.py +35 -1
  19. google/adk/agents/parallel_agent.py +24 -3
  20. google/adk/agents/remote_a2a_agent.py +17 -5
  21. google/adk/agents/sequential_agent.py +22 -1
  22. google/adk/artifacts/gcs_artifact_service.py +110 -20
  23. google/adk/auth/auth_handler.py +3 -3
  24. google/adk/auth/credential_manager.py +23 -23
  25. google/adk/auth/credential_service/base_credential_service.py +6 -6
  26. google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
  27. google/adk/auth/credential_service/session_state_credential_service.py +8 -8
  28. google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
  29. google/adk/auth/oauth2_credential_util.py +2 -2
  30. google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
  31. google/adk/cli/agent_graph.py +3 -1
  32. google/adk/cli/browser/index.html +2 -2
  33. google/adk/cli/browser/main-W7QZBYAR.js +3914 -0
  34. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  35. google/adk/cli/cli_eval.py +87 -12
  36. google/adk/cli/cli_tools_click.py +143 -82
  37. google/adk/cli/fast_api.py +150 -69
  38. google/adk/cli/utils/agent_loader.py +35 -1
  39. google/adk/code_executors/base_code_executor.py +14 -19
  40. google/adk/code_executors/built_in_code_executor.py +4 -1
  41. google/adk/evaluation/base_eval_service.py +46 -2
  42. google/adk/evaluation/eval_metrics.py +4 -0
  43. google/adk/evaluation/eval_sets_manager.py +5 -1
  44. google/adk/evaluation/evaluation_generator.py +1 -1
  45. google/adk/evaluation/final_response_match_v2.py +2 -2
  46. google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
  47. google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
  48. google/adk/evaluation/local_eval_service.py +389 -0
  49. google/adk/evaluation/local_eval_set_results_manager.py +2 -2
  50. google/adk/evaluation/local_eval_sets_manager.py +24 -9
  51. google/adk/evaluation/metric_evaluator_registry.py +16 -6
  52. google/adk/evaluation/vertex_ai_eval_facade.py +7 -1
  53. google/adk/events/event.py +7 -2
  54. google/adk/flows/llm_flows/auto_flow.py +6 -11
  55. google/adk/flows/llm_flows/base_llm_flow.py +66 -29
  56. google/adk/flows/llm_flows/contents.py +16 -10
  57. google/adk/flows/llm_flows/functions.py +89 -52
  58. google/adk/memory/in_memory_memory_service.py +21 -15
  59. google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
  60. google/adk/models/anthropic_llm.py +46 -6
  61. google/adk/models/base_llm_connection.py +2 -0
  62. google/adk/models/gemini_llm_connection.py +17 -6
  63. google/adk/models/google_llm.py +46 -11
  64. google/adk/models/lite_llm.py +52 -22
  65. google/adk/plugins/__init__.py +17 -0
  66. google/adk/plugins/base_plugin.py +317 -0
  67. google/adk/plugins/plugin_manager.py +265 -0
  68. google/adk/runners.py +122 -18
  69. google/adk/sessions/database_session_service.py +51 -52
  70. google/adk/sessions/vertex_ai_session_service.py +27 -12
  71. google/adk/tools/__init__.py +2 -0
  72. google/adk/tools/_automatic_function_calling_util.py +20 -2
  73. google/adk/tools/agent_tool.py +15 -3
  74. google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
  75. google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
  76. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
  77. google/adk/tools/base_tool.py +9 -9
  78. google/adk/tools/base_toolset.py +29 -5
  79. google/adk/tools/bigquery/__init__.py +3 -3
  80. google/adk/tools/bigquery/metadata_tool.py +2 -0
  81. google/adk/tools/bigquery/query_tool.py +15 -1
  82. google/adk/tools/computer_use/__init__.py +13 -0
  83. google/adk/tools/computer_use/base_computer.py +265 -0
  84. google/adk/tools/computer_use/computer_use_tool.py +166 -0
  85. google/adk/tools/computer_use/computer_use_toolset.py +220 -0
  86. google/adk/tools/enterprise_search_tool.py +4 -2
  87. google/adk/tools/exit_loop_tool.py +1 -0
  88. google/adk/tools/google_api_tool/google_api_tool.py +16 -1
  89. google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
  90. google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
  91. google/adk/tools/google_search_tool.py +4 -2
  92. google/adk/tools/langchain_tool.py +16 -6
  93. google/adk/tools/long_running_tool.py +21 -0
  94. google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
  95. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
  96. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
  97. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
  98. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
  99. google/adk/tools/tool_context.py +0 -10
  100. google/adk/tools/url_context_tool.py +4 -2
  101. google/adk/tools/vertex_ai_search_tool.py +4 -2
  102. google/adk/utils/model_name_utils.py +90 -0
  103. google/adk/version.py +1 -1
  104. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/METADATA +3 -2
  105. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/RECORD +108 -91
  106. google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
  107. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  108. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
  109. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
  110. {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,14 +14,17 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import importlib
17
18
  import inspect
18
19
  import logging
19
20
  from typing import Any
20
21
  from typing import AsyncGenerator
21
22
  from typing import Awaitable
22
23
  from typing import Callable
24
+ from typing import List
23
25
  from typing import Literal
24
26
  from typing import Optional
27
+ from typing import Type
25
28
  from typing import Union
26
29
 
27
30
  from google.genai import types
@@ -48,8 +51,11 @@ from ..tools.base_tool import BaseTool
48
51
  from ..tools.base_toolset import BaseToolset
49
52
  from ..tools.function_tool import FunctionTool
50
53
  from ..tools.tool_context import ToolContext
54
+ from ..utils.feature_decorator import working_in_progress
51
55
  from .base_agent import BaseAgent
56
+ from .base_agent import BaseAgentConfig
52
57
  from .callback_context import CallbackContext
58
+ from .common_configs import CodeConfig
53
59
  from .invocation_context import InvocationContext
54
60
  from .readonly_context import ReadonlyContext
55
61
 
@@ -164,9 +170,9 @@ class LlmAgent(BaseAgent):
164
170
  """Controls content inclusion in model requests.
165
171
 
166
172
  Options:
167
- default: Model receives relevant conversation history
168
- none: Model receives no prior history, operates solely on current
169
- instruction and input
173
+ default: Model receives relevant conversation history
174
+ none: Model receives no prior history, operates solely on current
175
+ instruction and input
170
176
  """
171
177
 
172
178
  # Controlled input/output configurations - Start
@@ -175,8 +181,9 @@ class LlmAgent(BaseAgent):
175
181
  output_schema: Optional[type[BaseModel]] = None
176
182
  """The output schema when agent replies.
177
183
 
178
- NOTE: when this is set, agent can ONLY reply and CANNOT use any tools, such as
179
- function tools, RAGs, agent transfer, etc.
184
+ NOTE:
185
+ When this is set, agent can ONLY reply and CANNOT use any tools, such as
186
+ function tools, RAGs, agent transfer, etc.
180
187
  """
181
188
  output_key: Optional[str] = None
182
189
  """The key in session state to store the output of the agent.
@@ -191,9 +198,9 @@ class LlmAgent(BaseAgent):
191
198
  planner: Optional[BasePlanner] = None
192
199
  """Instructs the agent to make a plan and execute it step by step.
193
200
 
194
- NOTE: to use model's built-in thinking features, set the `thinking_config`
195
- field in `google.adk.planners.built_in_planner`.
196
-
201
+ NOTE:
202
+ To use model's built-in thinking features, set the `thinking_config`
203
+ field in `google.adk.planners.built_in_planner`.
197
204
  """
198
205
 
199
206
  code_executor: Optional[BaseCodeExecutor] = None
@@ -202,7 +209,8 @@ class LlmAgent(BaseAgent):
202
209
 
203
210
  Check out available code executions in `google.adk.code_executor` package.
204
211
 
205
- NOTE: to use model's built-in code executor, use the `BuiltInCodeExecutor`.
212
+ NOTE:
213
+ To use model's built-in code executor, use the `BuiltInCodeExecutor`.
206
214
  """
207
215
  # Advance features - End
208
216
 
@@ -516,5 +524,189 @@ class LlmAgent(BaseAgent):
516
524
  )
517
525
  return generate_content_config
518
526
 
527
+ @classmethod
528
+ @working_in_progress('LlmAgent._resolve_tools is not ready for use.')
529
+ def _resolve_tools(cls, tools_config: list[CodeConfig]) -> list[Any]:
530
+ """Resolve tools from configuration.
531
+
532
+ Args:
533
+ tools_config: List of tool configurations (CodeConfig objects).
534
+
535
+ Returns:
536
+ List of resolved tool objects.
537
+ """
538
+
539
+ resolved_tools = []
540
+ for tool_config in tools_config:
541
+ if '.' not in tool_config.name:
542
+ module = importlib.import_module('google.adk.tools')
543
+ obj = getattr(module, tool_config.name)
544
+ if isinstance(obj, ToolUnion):
545
+ resolved_tools.append(obj)
546
+ else:
547
+ raise ValueError(
548
+ f'Invalid tool name: {tool_config.name} is not a built-in tool.'
549
+ )
550
+ else:
551
+ from .config_agent_utils import resolve_code_reference
552
+
553
+ resolved_tools.append(resolve_code_reference(tool_config))
554
+
555
+ return resolved_tools
556
+
557
+ @classmethod
558
+ @override
559
+ @working_in_progress('LlmAgent.from_config is not ready for use.')
560
+ def from_config(
561
+ cls: Type[LlmAgent],
562
+ config: LlmAgentConfig,
563
+ config_abs_path: str,
564
+ ) -> LlmAgent:
565
+ from .config_agent_utils import resolve_callbacks
566
+ from .config_agent_utils import resolve_code_reference
567
+
568
+ agent = super().from_config(config, config_abs_path)
569
+ if config.model:
570
+ agent.model = config.model
571
+ if config.instruction:
572
+ agent.instruction = config.instruction
573
+ if config.disallow_transfer_to_parent:
574
+ agent.disallow_transfer_to_parent = config.disallow_transfer_to_parent
575
+ if config.disallow_transfer_to_peers:
576
+ agent.disallow_transfer_to_peers = config.disallow_transfer_to_peers
577
+ if config.include_contents != 'default':
578
+ agent.include_contents = config.include_contents
579
+ if config.input_schema:
580
+ agent.input_schema = resolve_code_reference(config.input_schema)
581
+ if config.output_schema:
582
+ agent.output_schema = resolve_code_reference(config.output_schema)
583
+ if config.output_key:
584
+ agent.output_key = config.output_key
585
+ if config.tools:
586
+ agent.tools = cls._resolve_tools(config.tools)
587
+ if config.before_model_callbacks:
588
+ agent.before_model_callback = resolve_callbacks(
589
+ config.before_model_callbacks
590
+ )
591
+ if config.after_model_callbacks:
592
+ agent.after_model_callback = resolve_callbacks(
593
+ config.after_model_callbacks
594
+ )
595
+ if config.before_tool_callbacks:
596
+ agent.before_tool_callback = resolve_callbacks(
597
+ config.before_tool_callbacks
598
+ )
599
+ if config.after_tool_callbacks:
600
+ agent.after_tool_callback = resolve_callbacks(config.after_tool_callbacks)
601
+ return agent
602
+
519
603
 
520
604
  Agent: TypeAlias = LlmAgent
605
+
606
+
607
+ class LlmAgentConfig(BaseAgentConfig):
608
+ """The config for the YAML schema of a LlmAgent."""
609
+
610
+ agent_class: Literal['LlmAgent', ''] = 'LlmAgent'
611
+ """The value is used to uniquely identify the LlmAgent class. If it is
612
+ empty, it is by default an LlmAgent."""
613
+
614
+ model: Optional[str] = None
615
+ """Optional. LlmAgent.model. If not set, the model will be inherited from
616
+ the ancestor."""
617
+
618
+ instruction: str
619
+ """Required. LlmAgent.instruction."""
620
+
621
+ disallow_transfer_to_parent: Optional[bool] = None
622
+ """Optional. LlmAgent.disallow_transfer_to_parent."""
623
+
624
+ disallow_transfer_to_peers: Optional[bool] = None
625
+ """Optional. LlmAgent.disallow_transfer_to_peers."""
626
+
627
+ input_schema: Optional[CodeConfig] = None
628
+ """Optional. LlmAgent.input_schema."""
629
+
630
+ output_schema: Optional[CodeConfig] = None
631
+ """Optional. LlmAgent.output_schema."""
632
+
633
+ output_key: Optional[str] = None
634
+ """Optional. LlmAgent.output_key."""
635
+
636
+ include_contents: Literal['default', 'none'] = 'default'
637
+ """Optional. LlmAgent.include_contents."""
638
+
639
+ tools: Optional[list[CodeConfig]] = None
640
+ """Optional. LlmAgent.tools.
641
+
642
+ Examples:
643
+
644
+ For ADK built-in tools in `google.adk.tools` package, they can be referenced
645
+ directly with the name:
646
+
647
+ ```
648
+ tools:
649
+ - name: google_search
650
+ - name: load_memory
651
+ ```
652
+
653
+ For user-defined tools, they can be referenced with fully qualified name:
654
+
655
+ ```
656
+ tools:
657
+ - name: my_library.my_tools.my_tool
658
+ ```
659
+
660
+ For tools that needs to be created via functions:
661
+
662
+ ```
663
+ tools:
664
+ - name: my_library.my_tools.create_tool
665
+ args:
666
+ - name: param1
667
+ value: value1
668
+ - name: param2
669
+ value: value2
670
+ ```
671
+
672
+ For more advanced tools, instead of specifying arguments in config, it's
673
+ recommended to define them in Python files and reference them. E.g.,
674
+
675
+ ```
676
+ # tools.py
677
+ my_mcp_toolset = MCPToolset(
678
+ connection_params=StdioServerParameters(
679
+ command="npx",
680
+ args=["-y", "@notionhq/notion-mcp-server"],
681
+ env={"OPENAPI_MCP_HEADERS": NOTION_HEADERS},
682
+ )
683
+ )
684
+ ```
685
+
686
+ Then, reference the toolset in config:
687
+
688
+ ```
689
+ tools:
690
+ - name: tools.my_mcp_toolset
691
+ ```
692
+ """
693
+
694
+ before_model_callbacks: Optional[List[CodeConfig]] = None
695
+ """Optional. LlmAgent.before_model_callbacks.
696
+
697
+ Example:
698
+
699
+ ```
700
+ before_model_callbacks:
701
+ - name: my_library.callbacks.before_model_callback
702
+ ```
703
+ """
704
+
705
+ after_model_callbacks: Optional[List[CodeConfig]] = None
706
+ """Optional. LlmAgent.after_model_callbacks."""
707
+
708
+ before_tool_callbacks: Optional[List[CodeConfig]] = None
709
+ """Optional. LlmAgent.before_tool_callbacks."""
710
+
711
+ after_tool_callbacks: Optional[List[CodeConfig]] = None
712
+ """Optional. LlmAgent.after_tool_callbacks."""
@@ -16,14 +16,20 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ from typing import Any
19
20
  from typing import AsyncGenerator
21
+ from typing import Dict
22
+ from typing import Literal
20
23
  from typing import Optional
24
+ from typing import Type
21
25
 
22
26
  from typing_extensions import override
23
27
 
24
28
  from ..agents.invocation_context import InvocationContext
25
29
  from ..events.event import Event
30
+ from ..utils.feature_decorator import working_in_progress
26
31
  from .base_agent import BaseAgent
32
+ from .base_agent import BaseAgentConfig
27
33
 
28
34
 
29
35
  class LoopAgent(BaseAgent):
@@ -47,10 +53,15 @@ class LoopAgent(BaseAgent):
47
53
  times_looped = 0
48
54
  while not self.max_iterations or times_looped < self.max_iterations:
49
55
  for sub_agent in self.sub_agents:
56
+ should_exit = False
50
57
  async for event in sub_agent.run_async(ctx):
51
58
  yield event
52
59
  if event.actions.escalate:
53
- return
60
+ should_exit = True
61
+
62
+ if should_exit:
63
+ return
64
+
54
65
  times_looped += 1
55
66
  return
56
67
 
@@ -60,3 +71,26 @@ class LoopAgent(BaseAgent):
60
71
  ) -> AsyncGenerator[Event, None]:
61
72
  raise NotImplementedError('This is not supported yet for LoopAgent.')
62
73
  yield # AsyncGenerator requires having at least one yield statement
74
+
75
+ @classmethod
76
+ @override
77
+ @working_in_progress('LoopAgent.from_config is not ready for use.')
78
+ def from_config(
79
+ cls: Type[LoopAgent],
80
+ config: LoopAgentConfig,
81
+ config_abs_path: str,
82
+ ) -> LoopAgent:
83
+ agent = super().from_config(config, config_abs_path)
84
+ if config.max_iterations:
85
+ agent.max_iterations = config.max_iterations
86
+ return agent
87
+
88
+
89
+ @working_in_progress('LoopAgentConfig is not ready for use.')
90
+ class LoopAgentConfig(BaseAgentConfig):
91
+ """The config for the YAML schema of a LoopAgent."""
92
+
93
+ agent_class: Literal['LoopAgent'] = 'LoopAgent'
94
+
95
+ max_iterations: Optional[int] = None
96
+ """Optional. LoopAgent.max_iterations."""
@@ -18,9 +18,13 @@ from __future__ import annotations
18
18
 
19
19
  import asyncio
20
20
  from typing import AsyncGenerator
21
+ from typing import Literal
22
+ from typing import Type
21
23
 
22
24
  from typing_extensions import override
23
25
 
26
+ from ..agents.base_agent import BaseAgentConfig
27
+ from ..agents.base_agent import working_in_progress
24
28
  from ..agents.invocation_context import InvocationContext
25
29
  from ..events.event import Event
26
30
  from .base_agent import BaseAgent
@@ -33,9 +37,9 @@ def _create_branch_ctx_for_sub_agent(
33
37
  ) -> InvocationContext:
34
38
  """Create isolated branch for every sub-agent."""
35
39
  invocation_context = invocation_context.model_copy()
36
- branch_suffix = f"{agent.name}.{sub_agent.name}"
40
+ branch_suffix = f'{agent.name}.{sub_agent.name}'
37
41
  invocation_context.branch = (
38
- f"{invocation_context.branch}.{branch_suffix}"
42
+ f'{invocation_context.branch}.{branch_suffix}'
39
43
  if invocation_context.branch
40
44
  else branch_suffix
41
45
  )
@@ -109,5 +113,22 @@ class ParallelAgent(BaseAgent):
109
113
  async def _run_live_impl(
110
114
  self, ctx: InvocationContext
111
115
  ) -> AsyncGenerator[Event, None]:
112
- raise NotImplementedError("This is not supported yet for ParallelAgent.")
116
+ raise NotImplementedError('This is not supported yet for ParallelAgent.')
113
117
  yield # AsyncGenerator requires having at least one yield statement
118
+
119
+ @classmethod
120
+ @override
121
+ @working_in_progress('ParallelAgent.from_config is not ready for use.')
122
+ def from_config(
123
+ cls: Type[ParallelAgent],
124
+ config: ParallelAgentConfig,
125
+ config_abs_path: str,
126
+ ) -> ParallelAgent:
127
+ return super().from_config(config, config_abs_path)
128
+
129
+
130
+ @working_in_progress('ParallelAgentConfig is not ready for use.')
131
+ class ParallelAgentConfig(BaseAgentConfig):
132
+ """The config for the YAML schema of a ParallelAgent."""
133
+
134
+ agent_class: Literal['ParallelAgent'] = 'ParallelAgent'
@@ -26,7 +26,7 @@ import uuid
26
26
 
27
27
  try:
28
28
  from a2a.client import A2AClient
29
- from a2a.client.client import A2ACardResolver # Import A2ACardResolver
29
+ from a2a.client.client import A2ACardResolver
30
30
  from a2a.types import AgentCard
31
31
  from a2a.types import Message as A2AMessage
32
32
  from a2a.types import MessageSendParams as A2AMessageSendParams
@@ -35,7 +35,6 @@ try:
35
35
  from a2a.types import SendMessageRequest
36
36
  from a2a.types import SendMessageSuccessResponse
37
37
  from a2a.types import Task as A2ATask
38
-
39
38
  except ImportError as e:
40
39
  import sys
41
40
 
@@ -46,6 +45,12 @@ except ImportError as e:
46
45
  else:
47
46
  raise e
48
47
 
48
+ try:
49
+ from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
50
+ except ImportError:
51
+ # Fallback for older versions of a2a-sdk.
52
+ AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent.json"
53
+
49
54
  from google.genai import types as genai_types
50
55
  import httpx
51
56
 
@@ -63,11 +68,18 @@ from ..flows.llm_flows.functions import find_matching_function_call
63
68
  from ..utils.feature_decorator import experimental
64
69
  from .base_agent import BaseAgent
65
70
 
71
+ __all__ = [
72
+ "A2AClientError",
73
+ "AGENT_CARD_WELL_KNOWN_PATH",
74
+ "AgentCardResolutionError",
75
+ "RemoteA2aAgent",
76
+ ]
77
+
78
+
66
79
  # Constants
67
80
  A2A_METADATA_PREFIX = "a2a:"
68
81
  DEFAULT_TIMEOUT = 600.0
69
82
 
70
-
71
83
  logger = logging.getLogger("google_adk." + __name__)
72
84
 
73
85
 
@@ -469,11 +481,11 @@ class RemoteA2aAgent(BaseAgent):
469
481
  ),
470
482
  )
471
483
 
472
- logger.info(build_a2a_request_log(a2a_request))
484
+ logger.debug(build_a2a_request_log(a2a_request))
473
485
 
474
486
  try:
475
487
  a2a_response = await self._a2a_client.send_message(request=a2a_request)
476
- logger.info(build_a2a_response_log(a2a_response))
488
+ logger.debug(build_a2a_response_log(a2a_response))
477
489
 
478
490
  event = await self._handle_a2a_response(a2a_response, ctx)
479
491
 
@@ -17,9 +17,13 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from typing import AsyncGenerator
20
+ from typing import Literal
21
+ from typing import Type
20
22
 
21
23
  from typing_extensions import override
22
24
 
25
+ from ..agents.base_agent import BaseAgentConfig
26
+ from ..agents.base_agent import working_in_progress
23
27
  from ..agents.invocation_context import InvocationContext
24
28
  from ..events.event import Event
25
29
  from .base_agent import BaseAgent
@@ -60,7 +64,7 @@ class SequentialAgent(BaseAgent):
60
64
  Signals that the model has successfully completed the user's question
61
65
  or task.
62
66
  """
63
- return "Task completion signaled."
67
+ return 'Task completion signaled.'
64
68
 
65
69
  if isinstance(sub_agent, LlmAgent):
66
70
  # Use function name to dedupe.
@@ -74,3 +78,20 @@ class SequentialAgent(BaseAgent):
74
78
  for sub_agent in self.sub_agents:
75
79
  async for event in sub_agent.run_live(ctx):
76
80
  yield event
81
+
82
+ @classmethod
83
+ @override
84
+ @working_in_progress('SequentialAgent.from_config is not ready for use.')
85
+ def from_config(
86
+ cls: Type[SequentialAgent],
87
+ config: SequentialAgentConfig,
88
+ config_abs_path: str,
89
+ ) -> SequentialAgent:
90
+ return super().from_config(config, config_abs_path)
91
+
92
+
93
+ @working_in_progress('SequentialAgentConfig is not ready for use.')
94
+ class SequentialAgentConfig(BaseAgentConfig):
95
+ """The config for the YAML schema of a SequentialAgent."""
96
+
97
+ agent_class: Literal['SequentialAgent'] = 'SequentialAgent'
@@ -12,9 +12,17 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """An artifact service implementation using Google Cloud Storage (GCS)."""
15
+ """An artifact service implementation using Google Cloud Storage (GCS).
16
+
17
+ The blob name format used depends on whether the filename has a user namespace:
18
+ - For files with user namespace (starting with "user:"):
19
+ {app_name}/{user_id}/user/{filename}/{version}
20
+ - For regular session-scoped files:
21
+ {app_name}/{user_id}/{session_id}/{filename}/{version}
22
+ """
16
23
  from __future__ import annotations
17
24
 
25
+ import asyncio
18
26
  import logging
19
27
  from typing import Optional
20
28
 
@@ -33,6 +41,7 @@ class GcsArtifactService(BaseArtifactService):
33
41
  def __init__(self, bucket_name: str, **kwargs):
34
42
  """Initializes the GcsArtifactService.
35
43
 
44
+
36
45
  Args:
37
46
  bucket_name: The name of the bucket to use.
38
47
  **kwargs: Keyword arguments to pass to the Google Cloud Storage client.
@@ -41,6 +50,79 @@ class GcsArtifactService(BaseArtifactService):
41
50
  self.storage_client = storage.Client(**kwargs)
42
51
  self.bucket = self.storage_client.bucket(self.bucket_name)
43
52
 
53
+ @override
54
+ async def save_artifact(
55
+ self,
56
+ *,
57
+ app_name: str,
58
+ user_id: str,
59
+ session_id: str,
60
+ filename: str,
61
+ artifact: types.Part,
62
+ ) -> int:
63
+ return await asyncio.to_thread(
64
+ self._save_artifact,
65
+ app_name,
66
+ user_id,
67
+ session_id,
68
+ filename,
69
+ artifact,
70
+ )
71
+
72
+ @override
73
+ async def load_artifact(
74
+ self,
75
+ *,
76
+ app_name: str,
77
+ user_id: str,
78
+ session_id: str,
79
+ filename: str,
80
+ version: Optional[int] = None,
81
+ ) -> Optional[types.Part]:
82
+ return await asyncio.to_thread(
83
+ self._load_artifact,
84
+ app_name,
85
+ user_id,
86
+ session_id,
87
+ filename,
88
+ version,
89
+ )
90
+
91
+ @override
92
+ async def list_artifact_keys(
93
+ self, *, app_name: str, user_id: str, session_id: str
94
+ ) -> list[str]:
95
+ return await asyncio.to_thread(
96
+ self._list_artifact_keys,
97
+ app_name,
98
+ user_id,
99
+ session_id,
100
+ )
101
+
102
+ @override
103
+ async def delete_artifact(
104
+ self, *, app_name: str, user_id: str, session_id: str, filename: str
105
+ ) -> None:
106
+ return await asyncio.to_thread(
107
+ self._delete_artifact,
108
+ app_name,
109
+ user_id,
110
+ session_id,
111
+ filename,
112
+ )
113
+
114
+ @override
115
+ async def list_versions(
116
+ self, *, app_name: str, user_id: str, session_id: str, filename: str
117
+ ) -> list[int]:
118
+ return await asyncio.to_thread(
119
+ self._list_versions,
120
+ app_name,
121
+ user_id,
122
+ session_id,
123
+ filename,
124
+ )
125
+
44
126
  def _file_has_user_namespace(self, filename: str) -> bool:
45
127
  """Checks if the filename has a user namespace.
46
128
 
@@ -77,17 +159,15 @@ class GcsArtifactService(BaseArtifactService):
77
159
  return f"{app_name}/{user_id}/user/{filename}/{version}"
78
160
  return f"{app_name}/{user_id}/{session_id}/{filename}/{version}"
79
161
 
80
- @override
81
- async def save_artifact(
162
+ def _save_artifact(
82
163
  self,
83
- *,
84
164
  app_name: str,
85
165
  user_id: str,
86
166
  session_id: str,
87
167
  filename: str,
88
168
  artifact: types.Part,
89
169
  ) -> int:
90
- versions = await self.list_versions(
170
+ versions = self._list_versions(
91
171
  app_name=app_name,
92
172
  user_id=user_id,
93
173
  session_id=session_id,
@@ -107,10 +187,8 @@ class GcsArtifactService(BaseArtifactService):
107
187
 
108
188
  return version
109
189
 
110
- @override
111
- async def load_artifact(
190
+ def _load_artifact(
112
191
  self,
113
- *,
114
192
  app_name: str,
115
193
  user_id: str,
116
194
  session_id: str,
@@ -118,7 +196,7 @@ class GcsArtifactService(BaseArtifactService):
118
196
  version: Optional[int] = None,
119
197
  ) -> Optional[types.Part]:
120
198
  if version is None:
121
- versions = await self.list_versions(
199
+ versions = self._list_versions(
122
200
  app_name=app_name,
123
201
  user_id=user_id,
124
202
  session_id=session_id,
@@ -141,9 +219,8 @@ class GcsArtifactService(BaseArtifactService):
141
219
  )
142
220
  return artifact
143
221
 
144
- @override
145
- async def list_artifact_keys(
146
- self, *, app_name: str, user_id: str, session_id: str
222
+ def _list_artifact_keys(
223
+ self, app_name: str, user_id: str, session_id: str
147
224
  ) -> list[str]:
148
225
  filenames = set()
149
226
 
@@ -165,11 +242,10 @@ class GcsArtifactService(BaseArtifactService):
165
242
 
166
243
  return sorted(list(filenames))
167
244
 
168
- @override
169
- async def delete_artifact(
170
- self, *, app_name: str, user_id: str, session_id: str, filename: str
245
+ def _delete_artifact(
246
+ self, app_name: str, user_id: str, session_id: str, filename: str
171
247
  ) -> None:
172
- versions = await self.list_versions(
248
+ versions = self._list_versions(
173
249
  app_name=app_name,
174
250
  user_id=user_id,
175
251
  session_id=session_id,
@@ -183,14 +259,28 @@ class GcsArtifactService(BaseArtifactService):
183
259
  blob.delete()
184
260
  return
185
261
 
186
- @override
187
- async def list_versions(
188
- self, *, app_name: str, user_id: str, session_id: str, filename: str
262
+ def _list_versions(
263
+ self, app_name: str, user_id: str, session_id: str, filename: str
189
264
  ) -> list[int]:
265
+ """Lists all available versions of an artifact.
266
+
267
+ This method retrieves all versions of a specific artifact by querying GCS blobs
268
+ that match the constructed blob name prefix.
269
+
270
+ Args:
271
+ app_name: The name of the application.
272
+ user_id: The ID of the user who owns the artifact.
273
+ session_id: The ID of the session (ignored for user-namespaced files).
274
+ filename: The name of the artifact file.
275
+
276
+ Returns:
277
+ A list of version numbers (integers) available for the specified artifact.
278
+ Returns an empty list if no versions are found.
279
+ """
190
280
  prefix = self._get_blob_name(app_name, user_id, session_id, filename, "")
191
281
  blobs = self.storage_client.list_blobs(self.bucket, prefix=prefix)
192
282
  versions = []
193
283
  for blob in blobs:
194
- _, _, _, _, version = blob.name.split("/")
284
+ *_, version = blob.name.split("/")
195
285
  versions.append(int(version))
196
286
  return versions