google-adk 0.5.0__py3-none-any.whl → 1.1.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/agents/base_agent.py +76 -30
- google/adk/agents/callback_context.py +2 -6
- google/adk/agents/llm_agent.py +122 -30
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +8 -0
- google/adk/agents/run_config.py +1 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +4 -2
- google/adk/artifacts/gcs_artifact_service.py +1 -1
- google/adk/artifacts/in_memory_artifact_service.py +1 -1
- google/adk/auth/auth_credential.py +10 -2
- google/adk/auth/auth_preprocessor.py +7 -1
- google/adk/auth/auth_tool.py +3 -4
- google/adk/cli/agent_graph.py +5 -5
- google/adk/cli/browser/index.html +4 -4
- google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -9
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +109 -115
- google/adk/cli/cli_tools_click.py +179 -67
- google/adk/cli/fast_api.py +248 -197
- google/adk/cli/utils/agent_loader.py +137 -0
- google/adk/cli/utils/cleanup.py +40 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -0
- google/adk/cli/utils/logs.py +8 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/code_executors/code_execution_utils.py +2 -1
- google/adk/code_executors/container_code_executor.py +0 -1
- google/adk/code_executors/vertex_ai_code_executor.py +6 -8
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +104 -0
- google/adk/evaluation/eval_metrics.py +74 -0
- google/adk/evaluation/eval_result.py +86 -0
- google/adk/evaluation/eval_set.py +39 -0
- google/adk/evaluation/eval_set_results_manager.py +47 -0
- google/adk/evaluation/eval_sets_manager.py +43 -0
- google/adk/evaluation/evaluation_generator.py +88 -113
- google/adk/evaluation/evaluator.py +58 -0
- google/adk/evaluation/local_eval_set_results_manager.py +113 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +106 -1
- google/adk/evaluation/trajectory_evaluator.py +84 -2
- google/adk/events/event.py +6 -1
- google/adk/events/event_actions.py +6 -1
- google/adk/examples/base_example_provider.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/llm_flows/_code_execution.py +9 -1
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +58 -21
- google/adk/flows/llm_flows/contents.py +3 -1
- google/adk/flows/llm_flows/functions.py +9 -8
- google/adk/flows/llm_flows/instructions.py +18 -80
- google/adk/flows/llm_flows/single_flow.py +2 -2
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +23 -21
- google/adk/memory/in_memory_memory_service.py +57 -25
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
- google/adk/models/anthropic_llm.py +16 -9
- google/adk/models/base_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +11 -11
- google/adk/models/google_llm.py +12 -2
- google/adk/models/lite_llm.py +80 -23
- google/adk/models/llm_response.py +16 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +98 -42
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +2 -1
- google/adk/sessions/base_session_service.py +6 -33
- google/adk/sessions/database_session_service.py +57 -67
- google/adk/sessions/in_memory_session_service.py +106 -24
- google/adk/sessions/session.py +3 -0
- google/adk/sessions/vertex_ai_session_service.py +44 -51
- google/adk/telemetry.py +7 -2
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +10 -10
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
- google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
- google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +96 -0
- google/adk/tools/bigquery/__init__.py +28 -0
- google/adk/tools/bigquery/bigquery_credentials.py +216 -0
- google/adk/tools/bigquery/bigquery_tool.py +116 -0
- google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
- google/adk/tools/function_parameter_parse_util.py +9 -2
- google/adk/tools/function_tool.py +33 -3
- google/adk/tools/get_user_choice_tool.py +1 -0
- google/adk/tools/google_api_tool/__init__.py +24 -70
- google/adk/tools/google_api_tool/google_api_tool.py +12 -6
- google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
- google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/google_search_tool.py +2 -2
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_memory_tool.py +14 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +6 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
- google/adk/tools/mcp_tool/mcp_tool.py +35 -32
- google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
- google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
- google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
- google/adk/tools/openapi_tool/common/common.py +5 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
- google/adk/tools/preload_memory_tool.py +27 -18
- google/adk/tools/retrieval/__init__.py +1 -1
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
- google/adk/tools/toolbox_toolset.py +107 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/utils/__init__.py +13 -0
- google/adk/utils/instructions_utils.py +131 -0
- google/adk/version.py +1 -1
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
- google_adk-1.1.0.dist-info/RECORD +200 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
- google/adk/cli/fast_api.py.orig +0 -728
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.5.0.dist-info/RECORD +0 -180
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,11 @@
|
|
14
14
|
|
15
15
|
import inspect
|
16
16
|
from textwrap import dedent
|
17
|
-
from typing import Any
|
17
|
+
from typing import Any
|
18
|
+
from typing import Dict
|
19
|
+
from typing import List
|
20
|
+
from typing import Optional
|
21
|
+
from typing import Union
|
18
22
|
|
19
23
|
from fastapi.encoders import jsonable_encoder
|
20
24
|
from fastapi.openapi.models import Operation
|
@@ -45,14 +49,14 @@ class OperationParser:
|
|
45
49
|
should_parse: Whether to parse the operation during initialization.
|
46
50
|
"""
|
47
51
|
if isinstance(operation, dict):
|
48
|
-
self.
|
52
|
+
self._operation = Operation.model_validate(operation)
|
49
53
|
elif isinstance(operation, str):
|
50
|
-
self.
|
54
|
+
self._operation = Operation.model_validate_json(operation)
|
51
55
|
else:
|
52
|
-
self.
|
56
|
+
self._operation = operation
|
53
57
|
|
54
|
-
self.
|
55
|
-
self.
|
58
|
+
self._params: List[ApiParameter] = []
|
59
|
+
self._return_value: Optional[ApiParameter] = None
|
56
60
|
if should_parse:
|
57
61
|
self._process_operation_parameters()
|
58
62
|
self._process_request_body()
|
@@ -67,13 +71,13 @@ class OperationParser:
|
|
67
71
|
return_value: Optional[ApiParameter] = None,
|
68
72
|
) -> 'OperationParser':
|
69
73
|
parser = cls(operation, should_parse=False)
|
70
|
-
parser.
|
71
|
-
parser.
|
74
|
+
parser._params = params
|
75
|
+
parser._return_value = return_value
|
72
76
|
return parser
|
73
77
|
|
74
78
|
def _process_operation_parameters(self):
|
75
79
|
"""Processes parameters from the OpenAPI operation."""
|
76
|
-
parameters = self.
|
80
|
+
parameters = self._operation.parameters or []
|
77
81
|
for param in parameters:
|
78
82
|
if isinstance(param, Parameter):
|
79
83
|
original_name = param.name
|
@@ -86,7 +90,7 @@ class OperationParser:
|
|
86
90
|
# param.required can be None
|
87
91
|
required = param.required if param.required is not None else False
|
88
92
|
|
89
|
-
self.
|
93
|
+
self._params.append(
|
90
94
|
ApiParameter(
|
91
95
|
original_name=original_name,
|
92
96
|
param_location=location,
|
@@ -98,7 +102,7 @@ class OperationParser:
|
|
98
102
|
|
99
103
|
def _process_request_body(self):
|
100
104
|
"""Processes the request body from the OpenAPI operation."""
|
101
|
-
request_body = self.
|
105
|
+
request_body = self._operation.requestBody
|
102
106
|
if not request_body:
|
103
107
|
return
|
104
108
|
|
@@ -114,7 +118,7 @@ class OperationParser:
|
|
114
118
|
if schema and schema.type == 'object':
|
115
119
|
properties = schema.properties or {}
|
116
120
|
for prop_name, prop_details in properties.items():
|
117
|
-
self.
|
121
|
+
self._params.append(
|
118
122
|
ApiParameter(
|
119
123
|
original_name=prop_name,
|
120
124
|
param_location='body',
|
@@ -124,7 +128,7 @@ class OperationParser:
|
|
124
128
|
)
|
125
129
|
|
126
130
|
elif schema and schema.type == 'array':
|
127
|
-
self.
|
131
|
+
self._params.append(
|
128
132
|
ApiParameter(
|
129
133
|
original_name='array',
|
130
134
|
param_location='body',
|
@@ -133,7 +137,7 @@ class OperationParser:
|
|
133
137
|
)
|
134
138
|
)
|
135
139
|
else:
|
136
|
-
self.
|
140
|
+
self._params.append(
|
137
141
|
# Empty name for unnamed body param
|
138
142
|
ApiParameter(
|
139
143
|
original_name='',
|
@@ -147,7 +151,7 @@ class OperationParser:
|
|
147
151
|
def _dedupe_param_names(self):
|
148
152
|
"""Deduplicates parameter names to avoid conflicts."""
|
149
153
|
params_cnt = {}
|
150
|
-
for param in self.
|
154
|
+
for param in self._params:
|
151
155
|
name = param.py_name
|
152
156
|
if name not in params_cnt:
|
153
157
|
params_cnt[name] = 0
|
@@ -157,7 +161,7 @@ class OperationParser:
|
|
157
161
|
|
158
162
|
def _process_return_value(self) -> Parameter:
|
159
163
|
"""Returns a Parameter object representing the return type."""
|
160
|
-
responses = self.
|
164
|
+
responses = self._operation.responses or {}
|
161
165
|
# Default to Any if no 2xx response or if schema is missing
|
162
166
|
return_schema = Schema(type='Any')
|
163
167
|
|
@@ -174,7 +178,7 @@ class OperationParser:
|
|
174
178
|
return_schema = content[mime_type].schema_
|
175
179
|
break
|
176
180
|
|
177
|
-
self.
|
181
|
+
self._return_value = ApiParameter(
|
178
182
|
original_name='',
|
179
183
|
param_location='',
|
180
184
|
param_schema=return_schema,
|
@@ -182,42 +186,42 @@ class OperationParser:
|
|
182
186
|
|
183
187
|
def get_function_name(self) -> str:
|
184
188
|
"""Returns the generated function name."""
|
185
|
-
operation_id = self.
|
189
|
+
operation_id = self._operation.operationId
|
186
190
|
if not operation_id:
|
187
191
|
raise ValueError('Operation ID is missing')
|
188
192
|
return to_snake_case(operation_id)[:60]
|
189
193
|
|
190
194
|
def get_return_type_hint(self) -> str:
|
191
195
|
"""Returns the return type hint string (like 'str', 'int', etc.)."""
|
192
|
-
return self.
|
196
|
+
return self._return_value.type_hint
|
193
197
|
|
194
198
|
def get_return_type_value(self) -> Any:
|
195
199
|
"""Returns the return type value (like str, int, List[str], etc.)."""
|
196
|
-
return self.
|
200
|
+
return self._return_value.type_value
|
197
201
|
|
198
202
|
def get_parameters(self) -> List[ApiParameter]:
|
199
203
|
"""Returns the list of Parameter objects."""
|
200
|
-
return self.
|
204
|
+
return self._params
|
201
205
|
|
202
206
|
def get_return_value(self) -> ApiParameter:
|
203
207
|
"""Returns the list of Parameter objects."""
|
204
|
-
return self.
|
208
|
+
return self._return_value
|
205
209
|
|
206
210
|
def get_auth_scheme_name(self) -> str:
|
207
211
|
"""Returns the name of the auth scheme for this operation from the spec."""
|
208
|
-
if self.
|
209
|
-
scheme_name = list(self.
|
212
|
+
if self._operation.security:
|
213
|
+
scheme_name = list(self._operation.security[0].keys())[0]
|
210
214
|
return scheme_name
|
211
215
|
return ''
|
212
216
|
|
213
217
|
def get_pydoc_string(self) -> str:
|
214
218
|
"""Returns the generated PyDoc string."""
|
215
|
-
pydoc_params = [param.to_pydoc_string() for param in self.
|
219
|
+
pydoc_params = [param.to_pydoc_string() for param in self._params]
|
216
220
|
pydoc_description = (
|
217
|
-
self.
|
221
|
+
self._operation.summary or self._operation.description or ''
|
218
222
|
)
|
219
223
|
pydoc_return = PydocHelper.generate_return_doc(
|
220
|
-
self.
|
224
|
+
self._operation.responses or {}
|
221
225
|
)
|
222
226
|
pydoc_arg_list = chr(10).join(
|
223
227
|
f' {param_doc}' for param_doc in pydoc_params
|
@@ -236,12 +240,12 @@ class OperationParser:
|
|
236
240
|
"""Returns the JSON schema for the function arguments."""
|
237
241
|
properties = {
|
238
242
|
p.py_name: jsonable_encoder(p.param_schema, exclude_none=True)
|
239
|
-
for p in self.
|
243
|
+
for p in self._params
|
240
244
|
}
|
241
245
|
return {
|
242
246
|
'properties': properties,
|
243
|
-
'required': [p.py_name for p in self.
|
244
|
-
'title': f"{self.
|
247
|
+
'required': [p.py_name for p in self._params if p.required],
|
248
|
+
'title': f"{self._operation.operationId or 'unnamed'}_Arguments",
|
245
249
|
'type': 'object',
|
246
250
|
}
|
247
251
|
|
@@ -253,11 +257,11 @@ class OperationParser:
|
|
253
257
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
254
258
|
annotation=param.type_value,
|
255
259
|
)
|
256
|
-
for param in self.
|
260
|
+
for param in self._params
|
257
261
|
]
|
258
262
|
|
259
263
|
def get_annotations(self) -> Dict[str, Any]:
|
260
264
|
"""Returns a dictionary of parameter annotations for the function."""
|
261
|
-
annotations = {p.py_name: p.type_value for p in self.
|
265
|
+
annotations = {p.py_name: p.type_value for p in self._params}
|
262
266
|
annotations['return'] = self.get_return_type_value()
|
263
267
|
return annotations
|
@@ -41,6 +41,16 @@ from .openapi_spec_parser import ParsedOperation
|
|
41
41
|
from .operation_parser import OperationParser
|
42
42
|
from .tool_auth_handler import ToolAuthHandler
|
43
43
|
|
44
|
+
# Not supported by the Gemini API
|
45
|
+
_OPENAPI_SCHEMA_IGNORE_FIELDS = (
|
46
|
+
"title",
|
47
|
+
"default",
|
48
|
+
"format",
|
49
|
+
"additional_properties",
|
50
|
+
"ref",
|
51
|
+
"def",
|
52
|
+
)
|
53
|
+
|
44
54
|
|
45
55
|
def snake_to_lower_camel(snake_case_string: str):
|
46
56
|
"""Converts a snake_case string to a lower_camel_case string.
|
@@ -121,7 +131,7 @@ def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
|
|
121
131
|
snake_case_key = to_snake_case(key)
|
122
132
|
# Check if the snake_case_key exists in the Schema model's fields.
|
123
133
|
if snake_case_key in Schema.model_fields:
|
124
|
-
if snake_case_key in
|
134
|
+
if snake_case_key in _OPENAPI_SCHEMA_IGNORE_FIELDS:
|
125
135
|
# Ignore these fields as Gemini backend doesn't recognize them, and will
|
126
136
|
# throw exception if they appear in the schema.
|
127
137
|
# Format: properties[expiration].format: only 'enum' and 'date-time' are
|
@@ -30,7 +30,7 @@ from ..auth.credential_exchangers.auto_auth_credential_exchanger import AutoAuth
|
|
30
30
|
from ..auth.credential_exchangers.base_credential_exchanger import AuthCredentialMissingError
|
31
31
|
from ..auth.credential_exchangers.base_credential_exchanger import BaseAuthCredentialExchanger
|
32
32
|
|
33
|
-
logger = logging.getLogger(__name__)
|
33
|
+
logger = logging.getLogger("google_adk." + __name__)
|
34
34
|
|
35
35
|
AuthPreparationState = Literal["pending", "done"]
|
36
36
|
|
@@ -14,11 +14,11 @@
|
|
14
14
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
|
-
from datetime import datetime
|
18
17
|
from typing import TYPE_CHECKING
|
19
18
|
|
20
19
|
from typing_extensions import override
|
21
20
|
|
21
|
+
from . import _memory_entry_utils
|
22
22
|
from .base_tool import BaseTool
|
23
23
|
from .tool_context import ToolContext
|
24
24
|
|
@@ -27,7 +27,10 @@ if TYPE_CHECKING:
|
|
27
27
|
|
28
28
|
|
29
29
|
class PreloadMemoryTool(BaseTool):
|
30
|
-
"""A tool that preloads the memory for the current user.
|
30
|
+
"""A tool that preloads the memory for the current user.
|
31
|
+
|
32
|
+
NOTE: Currently this tool only uses text part from the memory.
|
33
|
+
"""
|
31
34
|
|
32
35
|
def __init__(self):
|
33
36
|
# Name and description are not used because this tool only
|
@@ -41,29 +44,35 @@ class PreloadMemoryTool(BaseTool):
|
|
41
44
|
tool_context: ToolContext,
|
42
45
|
llm_request: LlmRequest,
|
43
46
|
) -> None:
|
44
|
-
|
45
|
-
if
|
47
|
+
user_content = tool_context.user_content
|
48
|
+
if (
|
49
|
+
not user_content
|
50
|
+
or not user_content.parts
|
51
|
+
or not user_content.parts[0].text
|
52
|
+
):
|
46
53
|
return
|
47
|
-
|
48
|
-
|
54
|
+
|
55
|
+
user_query: str = user_content.parts[0].text
|
56
|
+
response = await tool_context.search_memory(user_query)
|
49
57
|
if not response.memories:
|
50
58
|
return
|
51
|
-
|
59
|
+
|
60
|
+
memory_text_lines = []
|
52
61
|
for memory in response.memories:
|
53
|
-
time_str
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
62
|
+
if time_str := (f'Time: {memory.timestamp}' if memory.timestamp else ''):
|
63
|
+
memory_text_lines.append(time_str)
|
64
|
+
if memory_text := _memory_entry_utils.extract_text(memory):
|
65
|
+
memory_text_lines.append(
|
66
|
+
f'{memory.author}: {memory_text}' if memory.author else memory_text
|
67
|
+
)
|
68
|
+
if not memory_text_lines:
|
69
|
+
return
|
70
|
+
|
71
|
+
full_memory_text = '\n'.join(memory_text_lines)
|
63
72
|
si = f"""The following content is from your previous conversations with the user.
|
64
73
|
They may be useful for answering the user's current query.
|
65
74
|
<PAST_CONVERSATIONS>
|
66
|
-
{
|
75
|
+
{full_memory_text}
|
67
76
|
</PAST_CONVERSATIONS>
|
68
77
|
"""
|
69
78
|
llm_request.append_instructions([si])
|
@@ -29,7 +29,7 @@ try:
|
|
29
29
|
except ImportError:
|
30
30
|
import logging
|
31
31
|
|
32
|
-
logger = logging.getLogger(__name__)
|
32
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
33
33
|
logger.debug(
|
34
34
|
'The Vertex sdk is not installed. If you want to use the Vertex RAG with'
|
35
35
|
' agents, please install it. If not, you can ignore this warning.'
|
@@ -30,7 +30,7 @@ from .base_retrieval_tool import BaseRetrievalTool
|
|
30
30
|
if TYPE_CHECKING:
|
31
31
|
from ...models.llm_request import LlmRequest
|
32
32
|
|
33
|
-
logger = logging.getLogger(__name__)
|
33
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
34
34
|
|
35
35
|
|
36
36
|
class VertexAiRagRetrieval(BaseRetrievalTool):
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from typing import Any
|
16
|
+
from typing import Callable
|
17
|
+
from typing import List
|
18
|
+
from typing import Mapping
|
19
|
+
from typing import Optional
|
20
|
+
from typing import Union
|
21
|
+
|
22
|
+
import toolbox_core as toolbox
|
23
|
+
from typing_extensions import override
|
24
|
+
|
25
|
+
from ..agents.readonly_context import ReadonlyContext
|
26
|
+
from .base_tool import BaseTool
|
27
|
+
from .base_toolset import BaseToolset
|
28
|
+
from .function_tool import FunctionTool
|
29
|
+
|
30
|
+
|
31
|
+
class ToolboxToolset(BaseToolset):
|
32
|
+
"""A class that provides access to toolbox toolsets.
|
33
|
+
|
34
|
+
Example:
|
35
|
+
```python
|
36
|
+
toolbox_toolset = ToolboxToolset("http://127.0.0.1:5000",
|
37
|
+
toolset_name="my-toolset")
|
38
|
+
)
|
39
|
+
```
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(
|
43
|
+
self,
|
44
|
+
server_url: str,
|
45
|
+
toolset_name: Optional[str] = None,
|
46
|
+
tool_names: Optional[List[str]] = None,
|
47
|
+
auth_token_getters: Optional[dict[str, Callable[[], str]]] = None,
|
48
|
+
bound_params: Optional[
|
49
|
+
Mapping[str, Union[Callable[[], Any], Any]]
|
50
|
+
] = None,
|
51
|
+
):
|
52
|
+
"""Args:
|
53
|
+
|
54
|
+
server_url: The URL of the toolbox server.
|
55
|
+
toolset_name: The name of the toolbox toolset to load.
|
56
|
+
tool_names: The names of the tools to load.
|
57
|
+
auth_token_getters: A mapping of authentication service names to
|
58
|
+
callables that return the corresponding authentication token. see:
|
59
|
+
https://github.com/googleapis/mcp-toolbox-sdk-python/tree/main/packages/toolbox-core#authenticating-tools
|
60
|
+
for details.
|
61
|
+
bound_params: A mapping of parameter names to bind to specific values or
|
62
|
+
callables that are called to produce values as needed. see:
|
63
|
+
https://github.com/googleapis/mcp-toolbox-sdk-python/tree/main/packages/toolbox-core#binding-parameter-values
|
64
|
+
for details.
|
65
|
+
The resulting ToolboxToolset will contain both tools loaded by tool_names
|
66
|
+
and toolset_name.
|
67
|
+
"""
|
68
|
+
if not tool_names and not toolset_name:
|
69
|
+
raise ValueError("tool_names and toolset_name cannot both be None")
|
70
|
+
super().__init__()
|
71
|
+
self._server_url = server_url
|
72
|
+
self._toolbox_client = toolbox.ToolboxClient(server_url)
|
73
|
+
self._toolset_name = toolset_name
|
74
|
+
self._tool_names = tool_names
|
75
|
+
self._auth_token_getters = auth_token_getters or {}
|
76
|
+
self._bound_params = bound_params or {}
|
77
|
+
|
78
|
+
@override
|
79
|
+
async def get_tools(
|
80
|
+
self, readonly_context: Optional[ReadonlyContext] = None
|
81
|
+
) -> list[BaseTool]:
|
82
|
+
tools = []
|
83
|
+
if self._toolset_name:
|
84
|
+
tools.extend([
|
85
|
+
FunctionTool(tool)
|
86
|
+
for tool in await self._toolbox_client.load_toolset(
|
87
|
+
self._toolset_name,
|
88
|
+
auth_token_getters=self._auth_token_getters,
|
89
|
+
bound_params=self._bound_params,
|
90
|
+
)
|
91
|
+
])
|
92
|
+
if self._tool_names:
|
93
|
+
tools.extend([
|
94
|
+
FunctionTool(
|
95
|
+
await self._toolbox_client.load_tool(
|
96
|
+
tool_name,
|
97
|
+
auth_token_getters=self._auth_token_getters,
|
98
|
+
bound_params=self._bound_params,
|
99
|
+
)
|
100
|
+
)
|
101
|
+
for tool_name in self._tool_names
|
102
|
+
])
|
103
|
+
return tools
|
104
|
+
|
105
|
+
@override
|
106
|
+
async def close(self):
|
107
|
+
self._toolbox_client.close()
|
@@ -15,7 +15,6 @@
|
|
15
15
|
from .tool_context import ToolContext
|
16
16
|
|
17
17
|
|
18
|
-
# TODO: make this internal, since user doesn't need to use this tool directly.
|
19
18
|
def transfer_to_agent(agent_name: str, tool_context: ToolContext):
|
20
19
|
"""Transfer the question to another agent."""
|
21
20
|
tool_context.actions.transfer_to_agent = agent_name
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
import re
|
16
|
+
|
17
|
+
from ..agents.readonly_context import ReadonlyContext
|
18
|
+
from ..sessions.state import State
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
'inject_session_state',
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
async def inject_session_state(
|
26
|
+
template: str,
|
27
|
+
readonly_context: ReadonlyContext,
|
28
|
+
) -> str:
|
29
|
+
"""Populates values in the instruction template, e.g. state, artifact, etc.
|
30
|
+
|
31
|
+
This method is intended to be used in InstructionProvider based instruction
|
32
|
+
and global_instruction which are called with readonly_context.
|
33
|
+
|
34
|
+
e.g.
|
35
|
+
```
|
36
|
+
...
|
37
|
+
from google.adk.utils import instructions_utils
|
38
|
+
|
39
|
+
async def build_instruction(
|
40
|
+
readonly_context: ReadonlyContext,
|
41
|
+
) -> str:
|
42
|
+
return await instructions_utils.inject_session_state(
|
43
|
+
'You can inject a state variable like {var_name} or an artifact '
|
44
|
+
'{artifact.file_name} into the instruction template.',
|
45
|
+
readonly_context,
|
46
|
+
)
|
47
|
+
|
48
|
+
agent = Agent(
|
49
|
+
model="gemini-2.0-flash",
|
50
|
+
name="agent",
|
51
|
+
instruction=build_instruction,
|
52
|
+
)
|
53
|
+
```
|
54
|
+
|
55
|
+
Args:
|
56
|
+
template: The instruction template.
|
57
|
+
readonly_context: The read-only context
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
The instruction template with values populated.
|
61
|
+
"""
|
62
|
+
|
63
|
+
invocation_context = readonly_context._invocation_context
|
64
|
+
|
65
|
+
async def _async_sub(pattern, repl_async_fn, string) -> str:
|
66
|
+
result = []
|
67
|
+
last_end = 0
|
68
|
+
for match in re.finditer(pattern, string):
|
69
|
+
result.append(string[last_end : match.start()])
|
70
|
+
replacement = await repl_async_fn(match)
|
71
|
+
result.append(replacement)
|
72
|
+
last_end = match.end()
|
73
|
+
result.append(string[last_end:])
|
74
|
+
return ''.join(result)
|
75
|
+
|
76
|
+
async def _replace_match(match) -> str:
|
77
|
+
var_name = match.group().lstrip('{').rstrip('}').strip()
|
78
|
+
optional = False
|
79
|
+
if var_name.endswith('?'):
|
80
|
+
optional = True
|
81
|
+
var_name = var_name.removesuffix('?')
|
82
|
+
if var_name.startswith('artifact.'):
|
83
|
+
var_name = var_name.removeprefix('artifact.')
|
84
|
+
if invocation_context.artifact_service is None:
|
85
|
+
raise ValueError('Artifact service is not initialized.')
|
86
|
+
artifact = await invocation_context.artifact_service.load_artifact(
|
87
|
+
app_name=invocation_context.session.app_name,
|
88
|
+
user_id=invocation_context.session.user_id,
|
89
|
+
session_id=invocation_context.session.id,
|
90
|
+
filename=var_name,
|
91
|
+
)
|
92
|
+
if not var_name:
|
93
|
+
raise KeyError(f'Artifact {var_name} not found.')
|
94
|
+
return str(artifact)
|
95
|
+
else:
|
96
|
+
if not _is_valid_state_name(var_name):
|
97
|
+
return match.group()
|
98
|
+
if var_name in invocation_context.session.state:
|
99
|
+
return str(invocation_context.session.state[var_name])
|
100
|
+
else:
|
101
|
+
if optional:
|
102
|
+
return ''
|
103
|
+
else:
|
104
|
+
raise KeyError(f'Context variable not found: `{var_name}`.')
|
105
|
+
|
106
|
+
return await _async_sub(r'{+[^{}]*}+', _replace_match, template)
|
107
|
+
|
108
|
+
|
109
|
+
def _is_valid_state_name(var_name):
|
110
|
+
"""Checks if the variable name is a valid state name.
|
111
|
+
|
112
|
+
Valid state is either:
|
113
|
+
- Valid identifier
|
114
|
+
- <Valid prefix>:<Valid identifier>
|
115
|
+
All the others will just return as it is.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
var_name: The variable name to check.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
True if the variable name is a valid state name, False otherwise.
|
122
|
+
"""
|
123
|
+
parts = var_name.split(':')
|
124
|
+
if len(parts) == 1:
|
125
|
+
return var_name.isidentifier()
|
126
|
+
|
127
|
+
if len(parts) == 2:
|
128
|
+
prefixes = [State.APP_PREFIX, State.USER_PREFIX, State.TEMP_PREFIX]
|
129
|
+
if (parts[0] + ':') in prefixes:
|
130
|
+
return parts[1].isidentifier()
|
131
|
+
return False
|
google/adk/version.py
CHANGED