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.
- google/adk/a2a/converters/event_converter.py +5 -85
- google/adk/a2a/converters/request_converter.py +1 -2
- google/adk/a2a/executor/a2a_agent_executor.py +45 -16
- google/adk/a2a/logs/log_utils.py +1 -2
- google/adk/a2a/utils/__init__.py +0 -0
- google/adk/a2a/utils/agent_card_builder.py +544 -0
- google/adk/a2a/utils/agent_to_a2a.py +118 -0
- google/adk/agents/__init__.py +5 -0
- google/adk/agents/agent_config.py +46 -0
- google/adk/agents/base_agent.py +239 -41
- google/adk/agents/callback_context.py +41 -0
- google/adk/agents/common_configs.py +79 -0
- google/adk/agents/config_agent_utils.py +184 -0
- google/adk/agents/config_schemas/AgentConfig.json +566 -0
- google/adk/agents/invocation_context.py +5 -1
- google/adk/agents/live_request_queue.py +15 -0
- google/adk/agents/llm_agent.py +201 -9
- google/adk/agents/loop_agent.py +35 -1
- google/adk/agents/parallel_agent.py +24 -3
- google/adk/agents/remote_a2a_agent.py +17 -5
- google/adk/agents/sequential_agent.py +22 -1
- google/adk/artifacts/gcs_artifact_service.py +110 -20
- google/adk/auth/auth_handler.py +3 -3
- google/adk/auth/credential_manager.py +23 -23
- google/adk/auth/credential_service/base_credential_service.py +6 -6
- google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
- google/adk/auth/credential_service/session_state_credential_service.py +8 -8
- google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
- google/adk/auth/oauth2_credential_util.py +2 -2
- google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
- google/adk/cli/agent_graph.py +3 -1
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/main-W7QZBYAR.js +3914 -0
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli_eval.py +87 -12
- google/adk/cli/cli_tools_click.py +143 -82
- google/adk/cli/fast_api.py +150 -69
- google/adk/cli/utils/agent_loader.py +35 -1
- google/adk/code_executors/base_code_executor.py +14 -19
- google/adk/code_executors/built_in_code_executor.py +4 -1
- google/adk/evaluation/base_eval_service.py +46 -2
- google/adk/evaluation/eval_metrics.py +4 -0
- google/adk/evaluation/eval_sets_manager.py +5 -1
- google/adk/evaluation/evaluation_generator.py +1 -1
- google/adk/evaluation/final_response_match_v2.py +2 -2
- google/adk/evaluation/gcs_eval_sets_manager.py +2 -1
- google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
- google/adk/evaluation/local_eval_service.py +389 -0
- google/adk/evaluation/local_eval_set_results_manager.py +2 -2
- google/adk/evaluation/local_eval_sets_manager.py +24 -9
- google/adk/evaluation/metric_evaluator_registry.py +16 -6
- google/adk/evaluation/vertex_ai_eval_facade.py +7 -1
- google/adk/events/event.py +7 -2
- google/adk/flows/llm_flows/auto_flow.py +6 -11
- google/adk/flows/llm_flows/base_llm_flow.py +66 -29
- google/adk/flows/llm_flows/contents.py +16 -10
- google/adk/flows/llm_flows/functions.py +89 -52
- google/adk/memory/in_memory_memory_service.py +21 -15
- google/adk/memory/vertex_ai_memory_bank_service.py +12 -10
- google/adk/models/anthropic_llm.py +46 -6
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +17 -6
- google/adk/models/google_llm.py +46 -11
- google/adk/models/lite_llm.py +52 -22
- google/adk/plugins/__init__.py +17 -0
- google/adk/plugins/base_plugin.py +317 -0
- google/adk/plugins/plugin_manager.py +265 -0
- google/adk/runners.py +122 -18
- google/adk/sessions/database_session_service.py +51 -52
- google/adk/sessions/vertex_ai_session_service.py +27 -12
- google/adk/tools/__init__.py +2 -0
- google/adk/tools/_automatic_function_calling_util.py +20 -2
- google/adk/tools/agent_tool.py +15 -3
- google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
- google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
- google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
- google/adk/tools/base_tool.py +9 -9
- google/adk/tools/base_toolset.py +29 -5
- google/adk/tools/bigquery/__init__.py +3 -3
- google/adk/tools/bigquery/metadata_tool.py +2 -0
- google/adk/tools/bigquery/query_tool.py +15 -1
- google/adk/tools/computer_use/__init__.py +13 -0
- google/adk/tools/computer_use/base_computer.py +265 -0
- google/adk/tools/computer_use/computer_use_tool.py +166 -0
- google/adk/tools/computer_use/computer_use_toolset.py +220 -0
- google/adk/tools/enterprise_search_tool.py +4 -2
- google/adk/tools/exit_loop_tool.py +1 -0
- google/adk/tools/google_api_tool/google_api_tool.py +16 -1
- google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
- google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
- google/adk/tools/google_search_tool.py +4 -2
- google/adk/tools/langchain_tool.py +16 -6
- google/adk/tools/long_running_tool.py +21 -0
- google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +5 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
- google/adk/tools/tool_context.py +0 -10
- google/adk/tools/url_context_tool.py +4 -2
- google/adk/tools/vertex_ai_search_tool.py +4 -2
- google/adk/utils/model_name_utils.py +90 -0
- google/adk/version.py +1 -1
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/METADATA +3 -2
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/RECORD +108 -91
- google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/WHEEL +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.6.1.dist-info → google_adk-1.8.0.dist-info}/licenses/LICENSE +0 -0
google/adk/agents/llm_agent.py
CHANGED
@@ -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
|
-
|
168
|
-
|
169
|
-
|
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:
|
179
|
-
|
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:
|
195
|
-
|
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:
|
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."""
|
google/adk/agents/loop_agent.py
CHANGED
@@ -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
|
-
|
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
|
40
|
+
branch_suffix = f'{agent.name}.{sub_agent.name}'
|
37
41
|
invocation_context.branch = (
|
38
|
-
f
|
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(
|
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
|
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.
|
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.
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
145
|
-
|
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
|
-
|
169
|
-
|
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 =
|
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
|
-
|
187
|
-
|
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
|
-
_,
|
284
|
+
*_, version = blob.name.split("/")
|
195
285
|
versions.append(int(version))
|
196
286
|
return versions
|