forked from google/adk-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbase_toolset.py
More file actions
241 lines (192 loc) · 7.75 KB
/
Copy pathbase_toolset.py
File metadata and controls
241 lines (192 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from abc import ABC
from abc import abstractmethod
import copy
from typing import final
from typing import List
from typing import Optional
from typing import Protocol
from typing import runtime_checkable
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
from ..agents.readonly_context import ReadonlyContext
from ..auth.auth_tool import AuthConfig
from .base_tool import BaseTool
if TYPE_CHECKING:
from ..models.llm_request import LlmRequest
from .tool_configs import ToolArgsConfig
from .tool_context import ToolContext
@runtime_checkable
class ToolPredicate(Protocol):
"""Base class for a predicate that defines the interface to decide whether a
tool should be exposed to LLM. Toolset implementer could consider whether to
accept such instance in the toolset's constructor and apply the predicate in
get_tools method.
"""
def __call__(
self, tool: BaseTool, readonly_context: Optional[ReadonlyContext] = None
) -> bool:
"""Decide whether the passed-in tool should be exposed to LLM based on the
current context. True if the tool is usable by the LLM.
It's used to filter tools in the toolset.
"""
SelfToolset = TypeVar("SelfToolset", bound="BaseToolset")
class BaseToolset(ABC):
"""Base class for toolset.
A toolset is a collection of tools that can be used by an agent.
"""
def __init__(
self,
*,
tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
tool_name_prefix: Optional[str] = None,
):
"""Initialize the toolset.
Args:
tool_filter: Filter to apply to tools.
tool_name_prefix: The prefix to prepend to the names of the tools returned by the toolset.
"""
self.tool_filter = tool_filter
self.tool_name_prefix = tool_name_prefix
self._cached_invocation_id: Optional[str] = None
self._cached_prefixed_tools: Optional[list[BaseTool]] = None
self._use_invocation_cache = True
@abstractmethod
async def get_tools(
self,
readonly_context: Optional[ReadonlyContext] = None,
) -> list[BaseTool]:
"""Return all tools in the toolset based on the provided context.
Args:
readonly_context (ReadonlyContext, optional): Context used to filter tools
available to the agent. If None, all tools in the toolset are returned.
Returns:
list[BaseTool]: A list of tools available under the specified context.
"""
@final
async def get_tools_with_prefix(
self,
readonly_context: Optional[ReadonlyContext] = None,
) -> list[BaseTool]:
"""Return all tools with optional prefix applied to tool names.
This method calls get_tools() and applies prefixing if tool_name_prefix is provided.
Args:
readonly_context (ReadonlyContext, optional): Context used to filter tools
available to the agent. If None, all tools in the toolset are returned.
Returns:
list[BaseTool]: A list of tools with prefixed names if tool_name_prefix is provided.
"""
invocation_id = readonly_context.invocation_id if readonly_context else None
if (
self._use_invocation_cache
and self._cached_prefixed_tools is not None
and self._cached_invocation_id == invocation_id
):
return self._cached_prefixed_tools
tools = await self.get_tools(readonly_context)
if not self.tool_name_prefix:
self._cached_invocation_id = invocation_id
self._cached_prefixed_tools = tools
return tools
prefix = self.tool_name_prefix
# Create copies of tools to avoid modifying original instances
prefixed_tools = []
for tool in tools:
# Create a shallow copy of the tool
tool_copy = copy.copy(tool)
# Apply prefix to the copied tool
prefixed_name = f"{prefix}_{tool.name}"
tool_copy.name = prefixed_name
# Also update the function declaration name if the tool has one
# Use default parameters to capture the current values in the closure
def _create_prefixed_declaration(
original_get_declaration=tool._get_declaration,
prefixed_name=prefixed_name,
):
def _get_prefixed_declaration():
declaration = original_get_declaration()
if declaration is not None:
declaration.name = prefixed_name
return declaration
return None
return _get_prefixed_declaration
tool_copy._get_declaration = _create_prefixed_declaration()
prefixed_tools.append(tool_copy)
self._cached_invocation_id = invocation_id
self._cached_prefixed_tools = prefixed_tools
return prefixed_tools
async def close(self) -> None:
"""Performs cleanup and releases resources held by the toolset.
NOTE:
This method is invoked, for example, at the end of an agent server's
lifecycle or when the toolset is no longer needed. Implementations
should ensure that any open connections, files, or other managed
resources are properly released to prevent leaks.
"""
@classmethod
def from_config(
cls: Type[SelfToolset], config: ToolArgsConfig, config_abs_path: str
) -> SelfToolset:
"""Creates a toolset instance from a config.
Args:
config: The config for the tool.
config_abs_path: The absolute path to the config file that contains the
tool config.
Returns:
The toolset instance.
"""
raise ValueError(f"from_config() not implemented for toolset: {cls}")
def _is_tool_selected(
self, tool: BaseTool, readonly_context: Optional[ReadonlyContext]
) -> bool:
if not self.tool_filter:
return True
if isinstance(self.tool_filter, ToolPredicate):
return self.tool_filter(tool, readonly_context)
if isinstance(self.tool_filter, list):
return tool.name in self.tool_filter
return False
async def process_llm_request(
self, *, tool_context: ToolContext, llm_request: LlmRequest
) -> None:
"""Processes the outgoing LLM request for this toolset. This method will be
called before each tool processes the llm request.
Use cases:
- Instead of let each tool process the llm request, we can let the toolset
process the llm request. e.g. ComputerUseToolset can add computer use
tool to the llm request.
Args:
tool_context: The context of the tool.
llm_request: The outgoing LLM request, mutable this method.
"""
pass
def get_auth_config(self) -> Optional[AuthConfig]:
"""Returns the auth config for this toolset. ADK will make sure the
'exchanged_auth_credential' field in the config is populated with
ready-to-use credential (e.g. oauth token for OAuth flow) before calling
get_tools method or execute any tools returned by this toolset. Thus toolset
can use this credential either for tool listing or tool calling. If tool
calling needs a different credential from ADK client, call
tool_context.request_credential in the tool.
Toolsets that support authentication should override this method to return
an AuthConfig constructed from their auth_scheme, auth_credential, and
optional credential_key parameters.
Returns:
AuthConfig if the toolset has authentication configured, None otherwise.
"""
return None