langchain-arcade 1.1.0__py3-none-any.whl → 1.2.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.
- langchain_arcade/__init__.py +6 -2
- langchain_arcade/_utilities.py +132 -17
- langchain_arcade/manager.py +742 -129
- langchain_arcade-1.2.0.dist-info/METADATA +192 -0
- langchain_arcade-1.2.0.dist-info/RECORD +8 -0
- langchain_arcade-1.1.0.dist-info/METADATA +0 -57
- langchain_arcade-1.1.0.dist-info/RECORD +0 -8
- {langchain_arcade-1.1.0.dist-info → langchain_arcade-1.2.0.dist-info}/LICENSE +0 -0
- {langchain_arcade-1.1.0.dist-info → langchain_arcade-1.2.0.dist-info}/WHEEL +0 -0
langchain_arcade/manager.py
CHANGED
|
@@ -1,91 +1,414 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import warnings
|
|
2
3
|
from collections.abc import Iterator
|
|
3
|
-
from typing import Any, Optional
|
|
4
|
+
from typing import Any, Optional, Union
|
|
4
5
|
|
|
5
|
-
from arcadepy import Arcade
|
|
6
|
+
from arcadepy import NOT_GIVEN, Arcade, AsyncArcade
|
|
6
7
|
from arcadepy.types import ToolDefinition
|
|
7
8
|
from arcadepy.types.shared import AuthorizationResponse
|
|
8
9
|
from langchain_core.tools import StructuredTool
|
|
9
10
|
|
|
10
|
-
from langchain_arcade._utilities import
|
|
11
|
-
wrap_arcade_tool,
|
|
12
|
-
)
|
|
11
|
+
from langchain_arcade._utilities import wrap_arcade_tool
|
|
13
12
|
|
|
13
|
+
ClientType = Union[Arcade, AsyncArcade]
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
class LangChainToolManager:
|
|
16
17
|
"""
|
|
17
|
-
|
|
18
|
+
Base tool manager for LangChain framework.
|
|
19
|
+
Provides a common interface for both synchronous and asynchronous tool managers.
|
|
18
20
|
|
|
19
|
-
This class
|
|
20
|
-
|
|
21
|
+
This class handles the storage and retrieval of tool definitions and provides
|
|
22
|
+
common functionality used by both synchronous and asynchronous implementations.
|
|
21
23
|
"""
|
|
22
24
|
|
|
23
|
-
def __init__(
|
|
24
|
-
self,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) ->
|
|
28
|
-
"""
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
self._tools: dict[str, ToolDefinition] = {}
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def tools(self) -> list[str]:
|
|
30
|
+
"""
|
|
31
|
+
Get the list of tools by name in the manager.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A list of tool names (strings) currently stored in the manager.
|
|
35
|
+
"""
|
|
36
|
+
return list(self._tools.keys())
|
|
37
|
+
|
|
38
|
+
def __len__(self) -> int:
|
|
39
|
+
"""Return the number of tools in the manager."""
|
|
40
|
+
return len(self._tools)
|
|
41
|
+
|
|
42
|
+
def _get_client_config(self, **kwargs: Any) -> dict[str, Any]:
|
|
43
|
+
"""
|
|
44
|
+
Get the client configurations from environment variables and kwargs.
|
|
45
|
+
|
|
46
|
+
If api_key or base_url are in the kwargs, they will be used.
|
|
47
|
+
Otherwise, the environment variables ARCADE_API_KEY and ARCADE_BASE_URL will be used.
|
|
48
|
+
If both are provided, the kwargs will take precedence.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
**kwargs: Keyword arguments that may contain api_key and base_url.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A dictionary of client configuration parameters.
|
|
55
|
+
"""
|
|
56
|
+
client_kwargs = {
|
|
57
|
+
"api_key": kwargs.get("api_key", os.getenv("ARCADE_API_KEY")),
|
|
58
|
+
}
|
|
59
|
+
base_url = kwargs.get("base_url", os.getenv("ARCADE_BASE_URL"))
|
|
60
|
+
if base_url:
|
|
61
|
+
client_kwargs["base_url"] = base_url
|
|
62
|
+
return client_kwargs
|
|
63
|
+
|
|
64
|
+
def _get_tool_definition(self, tool_name: str) -> ToolDefinition:
|
|
65
|
+
"""
|
|
66
|
+
Get a tool definition by name, raising an error if not found.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
tool_name: The name of the tool to retrieve.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The ToolDefinition for the specified tool.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If the tool is not found in the manager.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
return self._tools[tool_name]
|
|
79
|
+
except KeyError:
|
|
80
|
+
raise ValueError(f"Tool '{tool_name}' not found in this manager instance")
|
|
81
|
+
|
|
82
|
+
def __getitem__(self, tool_name: str) -> ToolDefinition:
|
|
83
|
+
"""
|
|
84
|
+
Get a tool definition by name using dictionary-like access.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
tool_name: The name of the tool to retrieve.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The ToolDefinition for the specified tool.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If the tool is not found in the manager.
|
|
94
|
+
"""
|
|
95
|
+
return self._get_tool_definition(tool_name)
|
|
96
|
+
|
|
97
|
+
def requires_auth(self, tool_name: str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Check if a tool requires authorization.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
tool_name: The name of the tool to check.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if the tool requires authorization, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
tool_def = self._get_tool_definition(tool_name)
|
|
108
|
+
if tool_def.requirements is None:
|
|
109
|
+
return False
|
|
110
|
+
return tool_def.requirements.authorization is not None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ToolManager(LangChainToolManager):
|
|
114
|
+
"""
|
|
115
|
+
Synchronous Arcade tool manager for LangChain framework.
|
|
116
|
+
|
|
117
|
+
This class wraps Arcade tools as LangChain StructuredTool objects for integration
|
|
118
|
+
with synchronous operations.
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
122
|
+
>>> # Initialize with specific tools and toolkits
|
|
123
|
+
>>> manager.init_tools(tools=["Search.SearchGoogle"], toolkits=["Weather"])
|
|
124
|
+
>>> # Get tools as LangChain StructuredTools
|
|
125
|
+
>>> langchain_tools = manager.to_langchain()
|
|
126
|
+
>>> # Handle authorization for tools that require it
|
|
127
|
+
>>> if manager.requires_auth("Search.SearchGoogle"):
|
|
128
|
+
>>> auth_response = manager.authorize("Search.SearchGoogle", "user_123")
|
|
129
|
+
>>> manager.wait_for_auth(auth_response.id)
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(self, client: Optional[Arcade] = None, **kwargs: Any) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Initialize the ToolManager.
|
|
29
135
|
|
|
30
136
|
Example:
|
|
31
|
-
>>> manager =
|
|
32
|
-
>>>
|
|
33
|
-
>>>
|
|
34
|
-
>>> manager
|
|
35
|
-
>>>
|
|
36
|
-
>>> # retrieve all tools in a toolkit as langchain tools
|
|
37
|
-
>>> manager.get_tools(toolkits=["Search"])
|
|
38
|
-
>>>
|
|
39
|
-
>>> # clear and initialize new tools in the manager
|
|
40
|
-
>>> manager.init_tools(tools=["Search.SearchGoogle"], toolkits=["Search"])
|
|
137
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
138
|
+
>>> # or with an existing client
|
|
139
|
+
>>> client = Arcade(api_key="your-api-key")
|
|
140
|
+
>>> manager = ToolManager(client=client)
|
|
41
141
|
|
|
42
142
|
Args:
|
|
43
|
-
client: Optional Arcade client instance.
|
|
44
|
-
**kwargs: Additional keyword arguments to pass to the Arcade client.
|
|
143
|
+
client: Optional Arcade client instance. If not provided, one will be created.
|
|
144
|
+
**kwargs: Additional keyword arguments to pass to the Arcade client if creating one.
|
|
145
|
+
Common options include api_key and base_url.
|
|
45
146
|
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
arcade_kwargs["base_url"] = base_url
|
|
52
|
-
|
|
53
|
-
client = Arcade(**arcade_kwargs) # type: ignore[arg-type]
|
|
54
|
-
self.client = client
|
|
55
|
-
self._tools: dict[str, ToolDefinition] = {}
|
|
147
|
+
super().__init__()
|
|
148
|
+
if client is None:
|
|
149
|
+
client_kwargs = self._get_client_config(**kwargs)
|
|
150
|
+
client = Arcade(**client_kwargs)
|
|
151
|
+
self._client = client
|
|
56
152
|
|
|
57
153
|
@property
|
|
58
|
-
def
|
|
59
|
-
|
|
154
|
+
def definitions(self) -> list[ToolDefinition]:
|
|
155
|
+
"""
|
|
156
|
+
Get the list of tool definitions in the manager.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
A list of ToolDefinition objects currently stored in the manager.
|
|
160
|
+
"""
|
|
161
|
+
return list(self._tools.values())
|
|
60
162
|
|
|
61
163
|
def __iter__(self) -> Iterator[tuple[str, ToolDefinition]]:
|
|
164
|
+
"""
|
|
165
|
+
Iterate over the tools in the manager as (name, definition) pairs.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Iterator over (tool_name, tool_definition) tuples.
|
|
169
|
+
"""
|
|
62
170
|
yield from self._tools.items()
|
|
63
171
|
|
|
64
|
-
def
|
|
65
|
-
|
|
172
|
+
def to_langchain(
|
|
173
|
+
self, use_interrupts: bool = True, use_underscores: bool = True
|
|
174
|
+
) -> list[StructuredTool]:
|
|
175
|
+
"""
|
|
176
|
+
Get the tools in the manager as LangChain StructuredTool objects.
|
|
66
177
|
|
|
67
|
-
|
|
68
|
-
|
|
178
|
+
Args:
|
|
179
|
+
use_interrupts: Whether to use interrupts for the tool. This is useful
|
|
180
|
+
for LangGraph workflows where you need to handle tool
|
|
181
|
+
authorization through state transitions.
|
|
182
|
+
use_underscores: Whether to use underscores for the tool name instead of periods.
|
|
183
|
+
For example, "Search_SearchGoogle" vs "Search.SearchGoogle".
|
|
184
|
+
Some model providers like OpenAI work better with underscores.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of StructuredTool instances ready to use with LangChain.
|
|
188
|
+
"""
|
|
189
|
+
tool_map = _create_tool_map(self.definitions, use_underscores=use_underscores)
|
|
190
|
+
return [
|
|
191
|
+
wrap_arcade_tool(self._client, tool_name, definition, langgraph=use_interrupts)
|
|
192
|
+
for tool_name, definition in tool_map.items()
|
|
193
|
+
]
|
|
69
194
|
|
|
70
195
|
def init_tools(
|
|
71
196
|
self,
|
|
72
197
|
tools: Optional[list[str]] = None,
|
|
73
198
|
toolkits: Optional[list[str]] = None,
|
|
199
|
+
limit: Optional[int] = None,
|
|
200
|
+
offset: Optional[int] = None,
|
|
201
|
+
raise_on_empty: bool = True,
|
|
202
|
+
) -> list[StructuredTool]:
|
|
203
|
+
"""
|
|
204
|
+
Initialize the tools in the manager and return them as LangChain tools.
|
|
205
|
+
|
|
206
|
+
This will clear any existing tools in the manager and replace them with
|
|
207
|
+
the new tools specified by the tools and toolkits parameters.
|
|
208
|
+
|
|
209
|
+
Note: In version 2.0+, this method returns a list of StructuredTool objects.
|
|
210
|
+
In earlier versions, it returned None.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
214
|
+
>>> langchain_tools = manager.init_tools(tools=["Search.SearchGoogle"])
|
|
215
|
+
>>> # Use these tools with a LangChain chain or agent
|
|
216
|
+
>>> agent = Agent(tools=langchain_tools, llm=llm)
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
tools: Optional list of specific tool names to include (e.g., "Search.SearchGoogle").
|
|
220
|
+
toolkits: Optional list of toolkit names to include all tools from (e.g., "Search").
|
|
221
|
+
limit: Optional limit on the number of tools to retrieve per request.
|
|
222
|
+
offset: Optional offset for paginated requests.
|
|
223
|
+
raise_on_empty: Whether to raise an error if no tools or toolkits are provided.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List of StructuredTool instances ready to use with LangChain.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ValueError: If no tools or toolkits are provided and raise_on_empty is True.
|
|
230
|
+
"""
|
|
231
|
+
tools_list = self._retrieve_tool_definitions(tools, toolkits, raise_on_empty, limit, offset)
|
|
232
|
+
self._tools = _create_tool_map(tools_list)
|
|
233
|
+
return self.to_langchain()
|
|
234
|
+
|
|
235
|
+
def authorize(self, tool_name: str, user_id: str) -> AuthorizationResponse:
|
|
236
|
+
"""
|
|
237
|
+
Authorize a user for a specific tool.
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
241
|
+
>>> manager.init_tools(tools=["Gmail.SendEmail"])
|
|
242
|
+
>>> auth_response = manager.authorize("Gmail.SendEmail", "user_123")
|
|
243
|
+
>>> # auth_response.auth_url contains the URL for the user to authorize
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
tool_name: The name of the tool to authorize.
|
|
247
|
+
user_id: The user ID to authorize. This should be a unique identifier for the user.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
AuthorizationResponse containing authorization details, including the auth_url
|
|
251
|
+
that should be presented to the user to complete authorization.
|
|
252
|
+
"""
|
|
253
|
+
return self._client.tools.authorize(tool_name=tool_name, user_id=user_id)
|
|
254
|
+
|
|
255
|
+
def is_authorized(self, authorization_id: str) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
Check if a tool authorization is complete.
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
261
|
+
>>> auth_response = manager.authorize("Gmail.SendEmail", "user_123")
|
|
262
|
+
>>> # After user completes authorization
|
|
263
|
+
>>> is_complete = manager.is_authorized(auth_response.id)
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
authorization_id: The authorization ID to check. This can be the full AuthorizationResponse
|
|
267
|
+
object or just the ID string.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True if the authorization is completed, False otherwise.
|
|
271
|
+
"""
|
|
272
|
+
# Handle case where entire AuthorizationResponse object is passed
|
|
273
|
+
if hasattr(authorization_id, "id"):
|
|
274
|
+
authorization_id = authorization_id.id
|
|
275
|
+
|
|
276
|
+
response = self._client.auth.status(id=authorization_id)
|
|
277
|
+
if response:
|
|
278
|
+
return response.status == "completed"
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
def wait_for_auth(self, authorization_id: str) -> AuthorizationResponse:
|
|
282
|
+
"""
|
|
283
|
+
Wait for a tool authorization to complete. This method blocks until
|
|
284
|
+
the authorization is complete or fails.
|
|
285
|
+
|
|
286
|
+
Example:
|
|
287
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
288
|
+
>>> auth_response = manager.authorize("Gmail.SendEmail", "user_123")
|
|
289
|
+
>>> # Share auth_response.auth_url with the user
|
|
290
|
+
>>> # Wait for the user to complete authorization
|
|
291
|
+
>>> completed_auth = manager.wait_for_auth(auth_response.id)
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
authorization_id: The authorization ID to wait for. This can be the full
|
|
295
|
+
AuthorizationResponse object or just the ID string.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
AuthorizationResponse with the completed authorization details.
|
|
299
|
+
"""
|
|
300
|
+
# Handle case where entire AuthorizationResponse object is passed
|
|
301
|
+
if hasattr(authorization_id, "id"):
|
|
302
|
+
authorization_id = authorization_id.id
|
|
303
|
+
|
|
304
|
+
return self._client.auth.wait_for_completion(authorization_id)
|
|
305
|
+
|
|
306
|
+
def _retrieve_tool_definitions(
|
|
307
|
+
self,
|
|
308
|
+
tools: Optional[list[str]] = None,
|
|
309
|
+
toolkits: Optional[list[str]] = None,
|
|
310
|
+
raise_on_empty: bool = True,
|
|
311
|
+
limit: Optional[int] = None,
|
|
312
|
+
offset: Optional[int] = None,
|
|
313
|
+
) -> list[ToolDefinition]:
|
|
314
|
+
"""
|
|
315
|
+
Retrieve tool definitions from the Arcade client, accounting for pagination.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
tools: Optional list of specific tool names to include.
|
|
319
|
+
toolkits: Optional list of toolkit names to include all tools from.
|
|
320
|
+
raise_on_empty: Whether to raise an error if no tools or toolkits are provided.
|
|
321
|
+
limit: Optional limit on the number of tools to retrieve per request.
|
|
322
|
+
offset: Optional offset for paginated requests.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of ToolDefinition instances.
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
ValueError: If no tools or toolkits are provided and raise_on_empty is True.
|
|
329
|
+
"""
|
|
330
|
+
all_tools: list[ToolDefinition] = []
|
|
331
|
+
|
|
332
|
+
# If no specific tools or toolkits are requested, raise an error.
|
|
333
|
+
if not tools and not toolkits:
|
|
334
|
+
if raise_on_empty:
|
|
335
|
+
raise ValueError("No tools or toolkits provided to retrieve tool definitions.")
|
|
336
|
+
return []
|
|
337
|
+
|
|
338
|
+
# Retrieve individual tools if specified
|
|
339
|
+
if tools:
|
|
340
|
+
for tool_id in tools:
|
|
341
|
+
single_tool = self._client.tools.get(name=tool_id)
|
|
342
|
+
all_tools.append(single_tool)
|
|
343
|
+
|
|
344
|
+
# Retrieve tools from specified toolkits
|
|
345
|
+
if toolkits:
|
|
346
|
+
for tk in toolkits:
|
|
347
|
+
# Convert None to NOT_GIVEN for Stainless client
|
|
348
|
+
paginated_tools = self._client.tools.list(
|
|
349
|
+
toolkit=tk,
|
|
350
|
+
limit=limit if limit is not None else NOT_GIVEN,
|
|
351
|
+
offset=offset if offset is not None else NOT_GIVEN,
|
|
352
|
+
)
|
|
353
|
+
all_tools.extend(paginated_tools)
|
|
354
|
+
|
|
355
|
+
return all_tools
|
|
356
|
+
|
|
357
|
+
def add_tool(self, tool_name: str) -> None:
|
|
358
|
+
"""
|
|
359
|
+
Add a single tool to the manager by name.
|
|
360
|
+
|
|
361
|
+
Unlike init_tools(), this method preserves existing tools in the manager
|
|
362
|
+
and only adds the specified tool.
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
366
|
+
>>> manager.add_tool("Gmail.SendEmail")
|
|
367
|
+
>>> manager.add_tool("Search.SearchGoogle")
|
|
368
|
+
>>> # Get all tools including newly added ones
|
|
369
|
+
>>> all_tools = manager.to_langchain()
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
tool_name: The fully qualified name of the tool to add (e.g., "Search.SearchGoogle")
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
ValueError: If the tool cannot be found
|
|
376
|
+
"""
|
|
377
|
+
tool = self._client.tools.get(name=tool_name)
|
|
378
|
+
self._tools.update(_create_tool_map([tool]))
|
|
379
|
+
|
|
380
|
+
def add_toolkit(
|
|
381
|
+
self, toolkit_name: str, limit: Optional[int] = None, offset: Optional[int] = None
|
|
74
382
|
) -> None:
|
|
75
|
-
"""
|
|
383
|
+
"""
|
|
384
|
+
Add all tools from a specific toolkit to the manager.
|
|
76
385
|
|
|
77
|
-
|
|
386
|
+
Unlike init_tools(), this method preserves existing tools in the manager
|
|
387
|
+
and only adds the tools from the specified toolkit.
|
|
78
388
|
|
|
79
389
|
Example:
|
|
80
|
-
>>> manager =
|
|
81
|
-
>>> manager.
|
|
82
|
-
>>> manager.
|
|
390
|
+
>>> manager = ToolManager(api_key="your-api-key")
|
|
391
|
+
>>> manager.add_toolkit("Gmail")
|
|
392
|
+
>>> manager.add_toolkit("Search")
|
|
393
|
+
>>> # Get all tools including newly added ones
|
|
394
|
+
>>> all_tools = manager.to_langchain()
|
|
83
395
|
|
|
84
396
|
Args:
|
|
85
|
-
|
|
86
|
-
|
|
397
|
+
toolkit_name: The name of the toolkit to add (e.g., "Search")
|
|
398
|
+
limit: Optional limit on the number of tools to retrieve per request
|
|
399
|
+
offset: Optional offset for paginated requests
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
ValueError: If the toolkit cannot be found or has no tools
|
|
87
403
|
"""
|
|
88
|
-
|
|
404
|
+
tools = self._client.tools.list(
|
|
405
|
+
toolkit=toolkit_name,
|
|
406
|
+
limit=NOT_GIVEN if limit is None else limit,
|
|
407
|
+
offset=NOT_GIVEN if offset is None else offset,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
for tool in tools:
|
|
411
|
+
self._tools.update(_create_tool_map([tool]))
|
|
89
412
|
|
|
90
413
|
def get_tools(
|
|
91
414
|
self,
|
|
@@ -93,20 +416,11 @@ class ArcadeToolManager:
|
|
|
93
416
|
toolkits: Optional[list[str]] = None,
|
|
94
417
|
langgraph: bool = True,
|
|
95
418
|
) -> list[StructuredTool]:
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
Note: if tools/toolkits are provided, the manager will update it's
|
|
99
|
-
internal tools using a dictionary update by tool name.
|
|
100
|
-
|
|
101
|
-
If langgraph is True, the tools will be wrapped with LangGraph-specific
|
|
102
|
-
behavior such as NodeInterrupts for auth.
|
|
103
|
-
Note: Changed in 1.0.0 to default to True.
|
|
419
|
+
"""
|
|
420
|
+
DEPRECATED: Return the tools in the manager as LangChain StructuredTool objects.
|
|
104
421
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>>>
|
|
108
|
-
>>> # retrieve a specific tool as a langchain tool
|
|
109
|
-
>>> manager.get_tools(tools=["Search.SearchGoogle"])
|
|
422
|
+
This method is deprecated and will be removed in a future major version.
|
|
423
|
+
Please use `init_tools()` to initialize tools and `to_langchain()` to convert them.
|
|
110
424
|
|
|
111
425
|
Args:
|
|
112
426
|
tools: Optional list of tool names to include.
|
|
@@ -117,103 +431,402 @@ class ArcadeToolManager:
|
|
|
117
431
|
Returns:
|
|
118
432
|
List of StructuredTool instances.
|
|
119
433
|
"""
|
|
120
|
-
|
|
434
|
+
warnings.warn(
|
|
435
|
+
"get_tools() is deprecated and will be removed in the next major version. "
|
|
436
|
+
"Please use init_tools() to initialize tools and to_langchain() to convert them.",
|
|
437
|
+
DeprecationWarning,
|
|
438
|
+
stacklevel=2,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Support existing usage pattern
|
|
121
442
|
if tools or toolkits:
|
|
122
|
-
|
|
123
|
-
self._tools.update(new_tools)
|
|
124
|
-
elif len(self) == 0:
|
|
125
|
-
self.init_tools()
|
|
443
|
+
self.init_tools(tools=tools, toolkits=toolkits)
|
|
126
444
|
|
|
127
|
-
|
|
128
|
-
for tool_name, definition in self:
|
|
129
|
-
lc_tool = wrap_arcade_tool(self.client, tool_name, definition, langgraph)
|
|
130
|
-
langchain_tools.append(lc_tool)
|
|
131
|
-
return langchain_tools
|
|
445
|
+
return self.to_langchain(use_interrupts=langgraph)
|
|
132
446
|
|
|
133
|
-
|
|
134
|
-
|
|
447
|
+
|
|
448
|
+
class ArcadeToolManager(ToolManager):
|
|
449
|
+
"""
|
|
450
|
+
Deprecated alias for ToolManager.
|
|
451
|
+
|
|
452
|
+
ArcadeToolManager is deprecated and will be removed in the next major version.
|
|
453
|
+
Please use ToolManager instead.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
457
|
+
warnings.warn(
|
|
458
|
+
"ArcadeToolManager is deprecated and will be removed in the next major version. "
|
|
459
|
+
"Please use ToolManager instead.",
|
|
460
|
+
DeprecationWarning,
|
|
461
|
+
stacklevel=2,
|
|
462
|
+
)
|
|
463
|
+
super().__init__(*args, **kwargs)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class AsyncToolManager(LangChainToolManager):
|
|
467
|
+
"""
|
|
468
|
+
Async version of Arcade tool manager for LangChain framework.
|
|
469
|
+
|
|
470
|
+
This class wraps Arcade tools as LangChain StructuredTool objects for integration
|
|
471
|
+
with asynchronous operations.
|
|
472
|
+
|
|
473
|
+
Example:
|
|
474
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
475
|
+
>>> # Initialize with specific tools and toolkits
|
|
476
|
+
>>> await manager.init_tools(tools=["Search.SearchGoogle"], toolkits=["Weather"])
|
|
477
|
+
>>> # Get tools as LangChain StructuredTools
|
|
478
|
+
>>> langchain_tools = await manager.to_langchain()
|
|
479
|
+
>>> # Handle authorization for tools that require it
|
|
480
|
+
>>> if manager.requires_auth("Search.SearchGoogle"):
|
|
481
|
+
>>> auth_response = await manager.authorize("Search.SearchGoogle", "user_123")
|
|
482
|
+
>>> await manager.wait_for_auth(auth_response.id)
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
def __init__(
|
|
486
|
+
self,
|
|
487
|
+
client: Optional[AsyncArcade] = None,
|
|
488
|
+
**kwargs: Any,
|
|
489
|
+
) -> None:
|
|
490
|
+
"""
|
|
491
|
+
Initialize the AsyncToolManager.
|
|
135
492
|
|
|
136
493
|
Example:
|
|
137
|
-
>>> manager =
|
|
138
|
-
>>>
|
|
494
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
495
|
+
>>> # or with an existing client
|
|
496
|
+
>>> client = AsyncArcade(api_key="your-api-key")
|
|
497
|
+
>>> manager = AsyncToolManager(client=client)
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
client: Optional AsyncArcade client instance. If not provided, one will be created.
|
|
501
|
+
**kwargs: Additional keyword arguments to pass to the AsyncArcade client if creating one.
|
|
502
|
+
Common options include api_key and base_url.
|
|
503
|
+
"""
|
|
504
|
+
super().__init__()
|
|
505
|
+
if not client:
|
|
506
|
+
client_kwargs = self._get_client_config(**kwargs)
|
|
507
|
+
client = AsyncArcade(**client_kwargs)
|
|
508
|
+
self._client = client
|
|
509
|
+
|
|
510
|
+
@property
|
|
511
|
+
def definitions(self) -> list[ToolDefinition]:
|
|
512
|
+
"""
|
|
513
|
+
Get the list of tool definitions in the manager.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
A list of ToolDefinition objects currently stored in the manager.
|
|
517
|
+
"""
|
|
518
|
+
return list(self._tools.values())
|
|
519
|
+
|
|
520
|
+
def __iter__(self) -> Iterator[tuple[str, ToolDefinition]]:
|
|
521
|
+
"""
|
|
522
|
+
Iterate over the tools in the manager as (name, definition) pairs.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Iterator over (tool_name, tool_definition) tuples.
|
|
526
|
+
"""
|
|
527
|
+
yield from self._tools.items()
|
|
528
|
+
|
|
529
|
+
async def init_tools(
|
|
530
|
+
self,
|
|
531
|
+
tools: Optional[list[str]] = None,
|
|
532
|
+
toolkits: Optional[list[str]] = None,
|
|
533
|
+
limit: Optional[int] = None,
|
|
534
|
+
offset: Optional[int] = None,
|
|
535
|
+
raise_on_empty: bool = True,
|
|
536
|
+
) -> list[StructuredTool]:
|
|
537
|
+
"""
|
|
538
|
+
Initialize the tools in the manager asynchronously and return them as LangChain tools.
|
|
539
|
+
|
|
540
|
+
This will clear any existing tools in the manager and replace them with
|
|
541
|
+
the new tools specified by the tools and toolkits parameters.
|
|
542
|
+
|
|
543
|
+
Example:
|
|
544
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
545
|
+
>>> langchain_tools = await manager.init_tools(tools=["Search.SearchGoogle"])
|
|
546
|
+
>>> # Use these tools with a LangChain chain or agent
|
|
547
|
+
>>> agent = Agent(tools=langchain_tools, llm=llm)
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
tools: Optional list of specific tool names to include (e.g., "Search.SearchGoogle").
|
|
551
|
+
toolkits: Optional list of toolkit names to include all tools from (e.g., "Search").
|
|
552
|
+
limit: Optional limit on the number of tools to retrieve per request.
|
|
553
|
+
offset: Optional offset for paginated requests.
|
|
554
|
+
raise_on_empty: Whether to raise an error if no tools or toolkits are provided.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
List of StructuredTool instances ready to use with LangChain.
|
|
558
|
+
|
|
559
|
+
Raises:
|
|
560
|
+
ValueError: If no tools or toolkits are provided and raise_on_empty is True.
|
|
561
|
+
"""
|
|
562
|
+
tools_list = await self._retrieve_tool_definitions(
|
|
563
|
+
tools, toolkits, raise_on_empty, limit, offset
|
|
564
|
+
)
|
|
565
|
+
self._tools.update(_create_tool_map(tools_list))
|
|
566
|
+
return await self.to_langchain()
|
|
567
|
+
|
|
568
|
+
async def to_langchain(
|
|
569
|
+
self, use_interrupts: bool = True, use_underscores: bool = True
|
|
570
|
+
) -> list[StructuredTool]:
|
|
571
|
+
"""
|
|
572
|
+
Get the tools in the manager as LangChain StructuredTool objects asynchronously.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
use_interrupts: Whether to use interrupts for the tool. This is useful
|
|
576
|
+
for LangGraph workflows where you need to handle tool
|
|
577
|
+
authorization through state transitions.
|
|
578
|
+
use_underscores: Whether to use underscores for the tool name instead of periods.
|
|
579
|
+
For example, "Search_SearchGoogle" vs "Search.SearchGoogle".
|
|
580
|
+
Some model providers like OpenAI work better with underscores.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
List of StructuredTool instances ready to use with LangChain.
|
|
584
|
+
"""
|
|
585
|
+
tool_map = _create_tool_map(self.definitions, use_underscores=use_underscores)
|
|
586
|
+
return [
|
|
587
|
+
wrap_arcade_tool(self._client, tool_name, definition, langgraph=use_interrupts)
|
|
588
|
+
for tool_name, definition in tool_map.items()
|
|
589
|
+
]
|
|
590
|
+
|
|
591
|
+
async def authorize(self, tool_name: str, user_id: str) -> AuthorizationResponse:
|
|
592
|
+
"""
|
|
593
|
+
Authorize a user for a tool.
|
|
594
|
+
|
|
595
|
+
Example:
|
|
596
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
597
|
+
>>> await manager.init_tools(tools=["Gmail.SendEmail"])
|
|
598
|
+
>>> auth_response = await manager.authorize("Gmail.SendEmail", "user_123")
|
|
599
|
+
>>> # auth_response.auth_url contains the URL for the user to authorize
|
|
139
600
|
|
|
140
601
|
Args:
|
|
141
602
|
tool_name: The name of the tool to authorize.
|
|
142
|
-
user_id: The user ID to authorize.
|
|
603
|
+
user_id: The user ID to authorize. This should be a unique identifier for the user.
|
|
143
604
|
|
|
144
605
|
Returns:
|
|
145
|
-
AuthorizationResponse
|
|
606
|
+
AuthorizationResponse containing authorization details, including the auth_url
|
|
607
|
+
that should be presented to the user to complete authorization.
|
|
146
608
|
"""
|
|
147
|
-
return self.
|
|
609
|
+
return await self._client.tools.authorize(tool_name=tool_name, user_id=user_id)
|
|
148
610
|
|
|
149
|
-
def is_authorized(self, authorization_id: str) -> bool:
|
|
150
|
-
"""
|
|
611
|
+
async def is_authorized(self, authorization_id: str) -> bool:
|
|
612
|
+
"""
|
|
613
|
+
Check if a tool authorization is complete.
|
|
151
614
|
|
|
152
615
|
Example:
|
|
153
|
-
>>> manager =
|
|
154
|
-
>>> manager.
|
|
155
|
-
>>>
|
|
616
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
617
|
+
>>> auth_response = await manager.authorize("Gmail.SendEmail", "user_123")
|
|
618
|
+
>>> # After user completes authorization
|
|
619
|
+
>>> is_complete = await manager.is_authorized(auth_response.id)
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
authorization_id: The authorization ID to check. This can be the full AuthorizationResponse
|
|
623
|
+
object or just the ID string.
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
True if the authorization is completed, False otherwise.
|
|
156
627
|
"""
|
|
157
|
-
|
|
628
|
+
# Handle case where entire AuthorizationResponse object is passed
|
|
629
|
+
if hasattr(authorization_id, "id"):
|
|
630
|
+
authorization_id = authorization_id.id
|
|
158
631
|
|
|
159
|
-
|
|
160
|
-
|
|
632
|
+
auth_status = await self._client.auth.status(id=authorization_id)
|
|
633
|
+
return auth_status.status == "completed"
|
|
634
|
+
|
|
635
|
+
async def wait_for_auth(self, authorization_id: str) -> AuthorizationResponse:
|
|
636
|
+
"""
|
|
637
|
+
Wait for a tool authorization to complete. This method blocks until
|
|
638
|
+
the authorization is complete or fails.
|
|
161
639
|
|
|
162
640
|
Example:
|
|
163
|
-
>>> manager =
|
|
164
|
-
>>> manager.
|
|
165
|
-
>>>
|
|
166
|
-
>>>
|
|
167
|
-
>>>
|
|
168
|
-
|
|
641
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
642
|
+
>>> auth_response = await manager.authorize("Gmail.SendEmail", "user_123")
|
|
643
|
+
>>> # Share auth_response.auth_url with the user
|
|
644
|
+
>>> # Wait for the user to complete authorization
|
|
645
|
+
>>> completed_auth = await manager.wait_for_auth(auth_response.id)
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
authorization_id: The authorization ID to wait for. This can be the full
|
|
649
|
+
AuthorizationResponse object or just the ID string.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
AuthorizationResponse with the completed authorization details.
|
|
169
653
|
"""
|
|
170
|
-
|
|
654
|
+
# Handle case where entire AuthorizationResponse object is passed
|
|
655
|
+
if hasattr(authorization_id, "id"):
|
|
656
|
+
authorization_id = authorization_id.id
|
|
171
657
|
|
|
172
|
-
|
|
173
|
-
"""Check if a tool requires authorization."""
|
|
658
|
+
return await self._client.auth.wait_for_completion(authorization_id)
|
|
174
659
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
660
|
+
async def _retrieve_tool_definitions(
|
|
661
|
+
self,
|
|
662
|
+
tools: Optional[list[str]] = None,
|
|
663
|
+
toolkits: Optional[list[str]] = None,
|
|
664
|
+
raise_on_empty: bool = True,
|
|
665
|
+
limit: Optional[int] = None,
|
|
666
|
+
offset: Optional[int] = None,
|
|
667
|
+
) -> list[ToolDefinition]:
|
|
668
|
+
"""
|
|
669
|
+
Retrieve tool definitions asynchronously from the Arcade client, accounting for pagination.
|
|
179
670
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
671
|
+
Args:
|
|
672
|
+
tools: Optional list of specific tool names to include.
|
|
673
|
+
toolkits: Optional list of toolkit names to include all tools from.
|
|
674
|
+
raise_on_empty: Whether to raise an error if no tools or toolkits are provided.
|
|
675
|
+
limit: Optional limit on the number of tools to retrieve per request.
|
|
676
|
+
offset: Optional offset for paginated requests.
|
|
185
677
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
678
|
+
Returns:
|
|
679
|
+
List of ToolDefinition instances.
|
|
680
|
+
|
|
681
|
+
Raises:
|
|
682
|
+
ValueError: If no tools or toolkits are provided and raise_on_empty is True.
|
|
683
|
+
"""
|
|
190
684
|
all_tools: list[ToolDefinition] = []
|
|
191
685
|
|
|
686
|
+
# If no specific tools or toolkits are requested, raise an error.
|
|
687
|
+
if not tools and not toolkits:
|
|
688
|
+
if raise_on_empty:
|
|
689
|
+
raise ValueError("No tools or toolkits provided to retrieve tool definitions.")
|
|
690
|
+
return []
|
|
691
|
+
|
|
192
692
|
# First, gather single tools if the user specifically requested them.
|
|
193
693
|
if tools:
|
|
194
694
|
for tool_id in tools:
|
|
195
695
|
# ToolsResource.get(...) returns a single ToolDefinition.
|
|
196
|
-
single_tool = self.
|
|
696
|
+
single_tool = await self._client.tools.get(name=tool_id)
|
|
197
697
|
all_tools.append(single_tool)
|
|
198
698
|
|
|
199
699
|
# Next, gather tool definitions from any requested toolkits.
|
|
200
700
|
if toolkits:
|
|
201
701
|
for tk in toolkits:
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
702
|
+
# Convert None to NOT_GIVEN for Stainless client
|
|
703
|
+
paginated_tools = await self._client.tools.list(
|
|
704
|
+
toolkit=tk,
|
|
705
|
+
limit=NOT_GIVEN if limit is None else limit,
|
|
706
|
+
offset=NOT_GIVEN if offset is None else offset,
|
|
707
|
+
)
|
|
708
|
+
async for tool in paginated_tools:
|
|
709
|
+
all_tools.append(tool)
|
|
710
|
+
|
|
711
|
+
return all_tools
|
|
712
|
+
|
|
713
|
+
async def add_tool(self, tool_name: str) -> None:
|
|
714
|
+
"""
|
|
715
|
+
Add a single tool to the manager by name.
|
|
206
716
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
717
|
+
Unlike init_tools(), this method preserves existing tools in the manager
|
|
718
|
+
and only adds the specified tool.
|
|
719
|
+
|
|
720
|
+
Example:
|
|
721
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
722
|
+
>>> await manager.add_tool("Gmail.SendEmail")
|
|
723
|
+
>>> await manager.add_tool("Search.SearchGoogle")
|
|
724
|
+
>>> # Get all tools including newly added ones
|
|
725
|
+
>>> all_tools = await manager.to_langchain()
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
tool_name: The fully qualified name of the tool to add (e.g., "Search.SearchGoogle")
|
|
729
|
+
|
|
730
|
+
Raises:
|
|
731
|
+
ValueError: If the tool cannot be found
|
|
732
|
+
"""
|
|
733
|
+
tool = await self._client.tools.get(name=tool_name)
|
|
734
|
+
self._tools.update(_create_tool_map([tool]))
|
|
735
|
+
|
|
736
|
+
async def add_toolkit(
|
|
737
|
+
self, toolkit_name: str, limit: Optional[int] = None, offset: Optional[int] = None
|
|
738
|
+
) -> None:
|
|
739
|
+
"""
|
|
740
|
+
Add all tools from a specific toolkit to the manager.
|
|
741
|
+
|
|
742
|
+
Unlike init_tools(), this method preserves existing tools in the manager
|
|
743
|
+
and only adds the tools from the specified toolkit.
|
|
744
|
+
|
|
745
|
+
Example:
|
|
746
|
+
>>> manager = AsyncToolManager(api_key="your-api-key")
|
|
747
|
+
>>> await manager.add_toolkit("Gmail")
|
|
748
|
+
>>> await manager.add_toolkit("Search")
|
|
749
|
+
>>> # Get all tools including newly added ones
|
|
750
|
+
>>> all_tools = await manager.to_langchain()
|
|
751
|
+
|
|
752
|
+
Args:
|
|
753
|
+
toolkit_name: The name of the toolkit to add (e.g., "Search")
|
|
754
|
+
limit: Optional limit on the number of tools to retrieve per request
|
|
755
|
+
offset: Optional offset for paginated requests
|
|
756
|
+
|
|
757
|
+
Raises:
|
|
758
|
+
ValueError: If the toolkit cannot be found or has no tools
|
|
759
|
+
"""
|
|
760
|
+
paginated_tools = await self._client.tools.list(
|
|
761
|
+
toolkit=toolkit_name,
|
|
762
|
+
limit=NOT_GIVEN if limit is None else limit,
|
|
763
|
+
offset=NOT_GIVEN if offset is None else offset,
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
async for tool in paginated_tools:
|
|
767
|
+
self._tools.update(_create_tool_map([tool]))
|
|
768
|
+
|
|
769
|
+
async def get_tools(
|
|
770
|
+
self,
|
|
771
|
+
tools: Optional[list[str]] = None,
|
|
772
|
+
toolkits: Optional[list[str]] = None,
|
|
773
|
+
langgraph: bool = True,
|
|
774
|
+
) -> list[StructuredTool]:
|
|
775
|
+
"""
|
|
776
|
+
DEPRECATED: Return the tools in the manager as LangChain StructuredTool objects.
|
|
777
|
+
|
|
778
|
+
This method is deprecated and will be removed in a future major version.
|
|
779
|
+
Please use `init_tools()` to initialize tools and `to_langchain()` to convert them.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
tools: Optional list of tool names to include.
|
|
783
|
+
toolkits: Optional list of toolkits to include.
|
|
784
|
+
langgraph: Whether to use LangGraph-specific behavior
|
|
785
|
+
such as NodeInterrupts for auth.
|
|
786
|
+
|
|
787
|
+
Returns:
|
|
788
|
+
List of StructuredTool instances.
|
|
789
|
+
"""
|
|
790
|
+
warnings.warn(
|
|
791
|
+
"get_tools() is deprecated and will be removed in the next major version. "
|
|
792
|
+
"Please use init_tools() to initialize tools and to_langchain() to convert them.",
|
|
793
|
+
DeprecationWarning,
|
|
794
|
+
stacklevel=2,
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
# Support existing usage pattern
|
|
798
|
+
if tools or toolkits:
|
|
799
|
+
return await self.init_tools(tools=tools, toolkits=toolkits)
|
|
800
|
+
return []
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _create_tool_map(
|
|
804
|
+
tools: list[ToolDefinition],
|
|
805
|
+
use_underscores: bool = True,
|
|
806
|
+
) -> dict[str, ToolDefinition]:
|
|
807
|
+
"""
|
|
808
|
+
Build a dictionary that maps the "full_tool_name" to the tool definition.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
tools: List of ToolDefinition objects to map.
|
|
812
|
+
use_underscores: Whether to use underscores instead of periods in tool names.
|
|
813
|
+
For example, "Search_SearchGoogle" vs "Search.SearchGoogle".
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Dictionary mapping tool names to tool definitions.
|
|
817
|
+
|
|
818
|
+
Note:
|
|
819
|
+
This is a temporary solution to support the naming convention of certain model providers
|
|
820
|
+
like OpenAI, which work better with underscores in tool names.
|
|
821
|
+
"""
|
|
822
|
+
tool_map: dict[str, ToolDefinition] = {}
|
|
823
|
+
for tool in tools:
|
|
824
|
+
# Ensure toolkit name and tool name are not None before creating the key
|
|
825
|
+
toolkit_name = tool.toolkit.name if tool.toolkit and tool.toolkit.name else None
|
|
826
|
+
if toolkit_name and tool.name:
|
|
827
|
+
if use_underscores:
|
|
828
|
+
tool_name = f"{toolkit_name}_{tool.name}"
|
|
829
|
+
else:
|
|
830
|
+
tool_name = f"{toolkit_name}.{tool.name}"
|
|
831
|
+
tool_map[tool_name] = tool
|
|
832
|
+
return tool_map
|