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.
Files changed (139) hide show
  1. google/adk/agents/base_agent.py +76 -30
  2. google/adk/agents/callback_context.py +2 -6
  3. google/adk/agents/llm_agent.py +122 -30
  4. google/adk/agents/loop_agent.py +1 -1
  5. google/adk/agents/parallel_agent.py +7 -0
  6. google/adk/agents/readonly_context.py +8 -0
  7. google/adk/agents/run_config.py +1 -1
  8. google/adk/agents/sequential_agent.py +31 -0
  9. google/adk/agents/transcription_entry.py +4 -2
  10. google/adk/artifacts/gcs_artifact_service.py +1 -1
  11. google/adk/artifacts/in_memory_artifact_service.py +1 -1
  12. google/adk/auth/auth_credential.py +10 -2
  13. google/adk/auth/auth_preprocessor.py +7 -1
  14. google/adk/auth/auth_tool.py +3 -4
  15. google/adk/cli/agent_graph.py +5 -5
  16. google/adk/cli/browser/index.html +4 -4
  17. google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
  18. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  19. google/adk/cli/cli.py +10 -9
  20. google/adk/cli/cli_deploy.py +7 -2
  21. google/adk/cli/cli_eval.py +109 -115
  22. google/adk/cli/cli_tools_click.py +179 -67
  23. google/adk/cli/fast_api.py +248 -197
  24. google/adk/cli/utils/agent_loader.py +137 -0
  25. google/adk/cli/utils/cleanup.py +40 -0
  26. google/adk/cli/utils/common.py +23 -0
  27. google/adk/cli/utils/evals.py +83 -0
  28. google/adk/cli/utils/logs.py +8 -5
  29. google/adk/code_executors/__init__.py +3 -1
  30. google/adk/code_executors/built_in_code_executor.py +52 -0
  31. google/adk/code_executors/code_execution_utils.py +2 -1
  32. google/adk/code_executors/container_code_executor.py +0 -1
  33. google/adk/code_executors/vertex_ai_code_executor.py +6 -8
  34. google/adk/evaluation/__init__.py +1 -1
  35. google/adk/evaluation/agent_evaluator.py +168 -128
  36. google/adk/evaluation/eval_case.py +104 -0
  37. google/adk/evaluation/eval_metrics.py +74 -0
  38. google/adk/evaluation/eval_result.py +86 -0
  39. google/adk/evaluation/eval_set.py +39 -0
  40. google/adk/evaluation/eval_set_results_manager.py +47 -0
  41. google/adk/evaluation/eval_sets_manager.py +43 -0
  42. google/adk/evaluation/evaluation_generator.py +88 -113
  43. google/adk/evaluation/evaluator.py +58 -0
  44. google/adk/evaluation/local_eval_set_results_manager.py +113 -0
  45. google/adk/evaluation/local_eval_sets_manager.py +264 -0
  46. google/adk/evaluation/response_evaluator.py +106 -1
  47. google/adk/evaluation/trajectory_evaluator.py +84 -2
  48. google/adk/events/event.py +6 -1
  49. google/adk/events/event_actions.py +6 -1
  50. google/adk/examples/base_example_provider.py +1 -0
  51. google/adk/examples/example_util.py +3 -2
  52. google/adk/flows/llm_flows/_code_execution.py +9 -1
  53. google/adk/flows/llm_flows/audio_transcriber.py +4 -3
  54. google/adk/flows/llm_flows/base_llm_flow.py +58 -21
  55. google/adk/flows/llm_flows/contents.py +3 -1
  56. google/adk/flows/llm_flows/functions.py +9 -8
  57. google/adk/flows/llm_flows/instructions.py +18 -80
  58. google/adk/flows/llm_flows/single_flow.py +2 -2
  59. google/adk/memory/__init__.py +1 -1
  60. google/adk/memory/_utils.py +23 -0
  61. google/adk/memory/base_memory_service.py +23 -21
  62. google/adk/memory/in_memory_memory_service.py +57 -25
  63. google/adk/memory/memory_entry.py +37 -0
  64. google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
  65. google/adk/models/anthropic_llm.py +16 -9
  66. google/adk/models/base_llm.py +2 -1
  67. google/adk/models/base_llm_connection.py +2 -0
  68. google/adk/models/gemini_llm_connection.py +11 -11
  69. google/adk/models/google_llm.py +12 -2
  70. google/adk/models/lite_llm.py +80 -23
  71. google/adk/models/llm_response.py +16 -3
  72. google/adk/models/registry.py +1 -1
  73. google/adk/runners.py +98 -42
  74. google/adk/sessions/__init__.py +1 -1
  75. google/adk/sessions/_session_util.py +2 -1
  76. google/adk/sessions/base_session_service.py +6 -33
  77. google/adk/sessions/database_session_service.py +57 -67
  78. google/adk/sessions/in_memory_session_service.py +106 -24
  79. google/adk/sessions/session.py +3 -0
  80. google/adk/sessions/vertex_ai_session_service.py +44 -51
  81. google/adk/telemetry.py +7 -2
  82. google/adk/tools/__init__.py +4 -7
  83. google/adk/tools/_memory_entry_utils.py +30 -0
  84. google/adk/tools/agent_tool.py +10 -10
  85. google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
  86. google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
  87. google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
  88. google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
  89. google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
  90. google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
  91. google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
  92. google/adk/tools/base_toolset.py +96 -0
  93. google/adk/tools/bigquery/__init__.py +28 -0
  94. google/adk/tools/bigquery/bigquery_credentials.py +216 -0
  95. google/adk/tools/bigquery/bigquery_tool.py +116 -0
  96. google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
  97. google/adk/tools/function_parameter_parse_util.py +9 -2
  98. google/adk/tools/function_tool.py +33 -3
  99. google/adk/tools/get_user_choice_tool.py +1 -0
  100. google/adk/tools/google_api_tool/__init__.py +24 -70
  101. google/adk/tools/google_api_tool/google_api_tool.py +12 -6
  102. google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
  103. google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
  104. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
  105. google/adk/tools/google_search_tool.py +2 -2
  106. google/adk/tools/langchain_tool.py +96 -49
  107. google/adk/tools/load_memory_tool.py +14 -5
  108. google/adk/tools/mcp_tool/__init__.py +3 -2
  109. google/adk/tools/mcp_tool/conversion_utils.py +6 -2
  110. google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
  111. google/adk/tools/mcp_tool/mcp_tool.py +35 -32
  112. google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
  113. google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
  114. google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
  115. google/adk/tools/openapi_tool/common/common.py +5 -1
  116. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
  117. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
  118. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
  119. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
  120. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
  121. google/adk/tools/preload_memory_tool.py +27 -18
  122. google/adk/tools/retrieval/__init__.py +1 -1
  123. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
  124. google/adk/tools/toolbox_toolset.py +107 -0
  125. google/adk/tools/transfer_to_agent_tool.py +0 -1
  126. google/adk/utils/__init__.py +13 -0
  127. google/adk/utils/instructions_utils.py +131 -0
  128. google/adk/version.py +1 -1
  129. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
  130. google_adk-1.1.0.dist-info/RECORD +200 -0
  131. google/adk/agents/remote_agent.py +0 -50
  132. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
  133. google/adk/cli/fast_api.py.orig +0 -728
  134. google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
  135. google/adk/tools/toolbox_tool.py +0 -46
  136. google_adk-0.5.0.dist-info/RECORD +0 -180
  137. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
  138. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
  139. {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, Dict, List, Optional, Union
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.operation = Operation.model_validate(operation)
52
+ self._operation = Operation.model_validate(operation)
49
53
  elif isinstance(operation, str):
50
- self.operation = Operation.model_validate_json(operation)
54
+ self._operation = Operation.model_validate_json(operation)
51
55
  else:
52
- self.operation = operation
56
+ self._operation = operation
53
57
 
54
- self.params: List[ApiParameter] = []
55
- self.return_value: Optional[ApiParameter] = None
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.params = params
71
- parser.return_value = return_value
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.operation.parameters or []
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.params.append(
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.operation.requestBody
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.params.append(
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.params.append(
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.params.append(
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.params:
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.operation.responses or {}
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.return_value = ApiParameter(
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.operation.operationId
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.return_value.type_hint
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.return_value.type_value
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.params
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.return_value
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.operation.security:
209
- scheme_name = list(self.operation.security[0].keys())[0]
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.params]
219
+ pydoc_params = [param.to_pydoc_string() for param in self._params]
216
220
  pydoc_description = (
217
- self.operation.summary or self.operation.description or ''
221
+ self._operation.summary or self._operation.description or ''
218
222
  )
219
223
  pydoc_return = PydocHelper.generate_return_doc(
220
- self.operation.responses or {}
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.params
243
+ for p in self._params
240
244
  }
241
245
  return {
242
246
  'properties': properties,
243
- 'required': [p.py_name for p in self.params if p.required],
244
- 'title': f"{self.operation.operationId or 'unnamed'}_Arguments",
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.params
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.params}
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 ["title", "default", "format"]:
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
- parts = tool_context.user_content.parts
45
- if not parts or not parts[0].text:
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
- query = parts[0].text
48
- response = await tool_context.search_memory(query)
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
- memory_text = ''
59
+
60
+ memory_text_lines = []
52
61
  for memory in response.memories:
53
- time_str = datetime.fromtimestamp(memory.events[0].timestamp).isoformat()
54
- memory_text += f'Time: {time_str}\n'
55
- for event in memory.events:
56
- # TODO: support multi-part content.
57
- if (
58
- event.content
59
- and event.content.parts
60
- and event.content.parts[0].text
61
- ):
62
- memory_text += f'{event.author}: {event.content.parts[0].text}\n'
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
- {memory_text}
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
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # version: date+base_cl
16
- __version__ = "0.5.0"
16
+ __version__ = "1.1.0"