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.
@@ -1,3 +1,7 @@
1
- from .manager import ArcadeToolManager
1
+ from .manager import ArcadeToolManager, AsyncToolManager, ToolManager
2
2
 
3
- __all__ = ["ArcadeToolManager"]
3
+ __all__ = [
4
+ "ToolManager",
5
+ "AsyncToolManager",
6
+ "ArcadeToolManager", # Deprecated
7
+ ]
@@ -1,7 +1,7 @@
1
- from typing import Any, Callable
1
+ from typing import Any, Callable, Union
2
2
 
3
- from arcadepy import NOT_GIVEN, Arcade
4
- from arcadepy.types import ToolDefinition
3
+ from arcadepy import NOT_GIVEN, Arcade, AsyncArcade
4
+ from arcadepy.types import ExecuteToolResponse, ToolDefinition
5
5
  from langchain_core.runnables import RunnableConfig
6
6
  from langchain_core.tools import StructuredTool
7
7
  from pydantic import BaseModel, Field, create_model
@@ -68,6 +68,51 @@ def tool_definition_to_pydantic_model(tool_def: ToolDefinition) -> type[BaseMode
68
68
  )
69
69
 
70
70
 
71
+ def process_tool_execution_response(
72
+ execute_response: ExecuteToolResponse, tool_name: str, langgraph: bool
73
+ ) -> Any:
74
+ """Process the response from tool execution and handle errors appropriately.
75
+
76
+ Args:
77
+ execute_response: The response from tool execution
78
+ tool_name: The name of the tool that was executed
79
+ langgraph: Whether LangGraph-specific behavior is enabled
80
+
81
+ Returns:
82
+ The output value on success, or error details on failure
83
+ """
84
+ if execute_response.success and execute_response.output is not None:
85
+ return execute_response.output.value
86
+
87
+ # Extract detailed error information
88
+ error_details = {
89
+ "error": "Unknown error occurred",
90
+ "tool": tool_name,
91
+ }
92
+
93
+ if execute_response.output is not None and execute_response.output.error is not None:
94
+ error = execute_response.output.error
95
+ error_message = str(error.message) if hasattr(error, "message") else "Unknown error"
96
+ error_details["error"] = error_message
97
+
98
+ # Add all non-None optional error fields to the details
99
+ if (
100
+ hasattr(error, "additional_prompt_content")
101
+ and error.additional_prompt_content is not None
102
+ ):
103
+ error_details["additional_prompt_content"] = error.additional_prompt_content
104
+ if hasattr(error, "can_retry") and error.can_retry is not None:
105
+ error_details["can_retry"] = str(error.can_retry)
106
+ if hasattr(error, "developer_message") and error.developer_message is not None:
107
+ error_details["developer_message"] = str(error.developer_message)
108
+ if hasattr(error, "retry_after_ms") and error.retry_after_ms is not None:
109
+ error_details["retry_after_ms"] = str(error.retry_after_ms)
110
+
111
+ if langgraph:
112
+ raise NodeInterrupt(error_details)
113
+ return error_details
114
+
115
+
71
116
  def create_tool_function(
72
117
  client: Arcade,
73
118
  tool_name: str,
@@ -128,18 +173,13 @@ def create_tool_function(
128
173
  user_id=user_id if user_id is not None else NOT_GIVEN,
129
174
  )
130
175
 
131
- if execute_response.success:
132
- return execute_response.output.value # type: ignore[union-attr]
133
- error_message = str(execute_response.output.error) # type: ignore[union-attr]
134
- if langgraph:
135
- raise NodeInterrupt(error_message)
136
- return {"error": error_message}
176
+ return process_tool_execution_response(execute_response, tool_name, langgraph)
137
177
 
138
178
  return tool_function
139
179
 
140
180
 
141
181
  def wrap_arcade_tool(
142
- client: Arcade,
182
+ client: Union[Arcade, AsyncArcade],
143
183
  tool_name: str,
144
184
  tool_def: ToolDefinition,
145
185
  langgraph: bool = False,
@@ -161,13 +201,23 @@ def wrap_arcade_tool(
161
201
  args_schema = tool_definition_to_pydantic_model(tool_def)
162
202
 
163
203
  # Create the action function
164
- action_func = create_tool_function(
165
- client=client,
166
- tool_name=tool_name,
167
- tool_def=tool_def,
168
- args_schema=args_schema,
169
- langgraph=langgraph,
170
- )
204
+ if isinstance(client, Arcade):
205
+ action_func = create_tool_function(
206
+ client=client,
207
+ tool_name=tool_name,
208
+ tool_def=tool_def,
209
+ args_schema=args_schema,
210
+ langgraph=langgraph,
211
+ )
212
+ else:
213
+ # Use async tool function for AsyncArcade client
214
+ action_func = create_async_tool_function(
215
+ client=client,
216
+ tool_name=tool_name,
217
+ tool_def=tool_def,
218
+ args_schema=args_schema,
219
+ langgraph=langgraph,
220
+ )
171
221
 
172
222
  # Create the StructuredTool instance
173
223
  return StructuredTool.from_function(
@@ -177,3 +227,68 @@ def wrap_arcade_tool(
177
227
  args_schema=args_schema,
178
228
  inject_kwargs={"user_id"},
179
229
  )
230
+
231
+
232
+ def create_async_tool_function(
233
+ client: AsyncArcade,
234
+ tool_name: str,
235
+ tool_def: ToolDefinition,
236
+ args_schema: type[BaseModel],
237
+ langgraph: bool = False,
238
+ ) -> Callable:
239
+ """Create an async callable function to execute an Arcade tool.
240
+
241
+ Args:
242
+ client: The AsyncArcade client instance.
243
+ tool_name: The name of the tool to wrap.
244
+ tool_def: The ToolDefinition of the tool to wrap.
245
+ args_schema: The Pydantic model representing the tool's arguments.
246
+ langgraph: Whether to enable LangGraph-specific behavior.
247
+
248
+ Returns:
249
+ An async callable function that executes the tool.
250
+ """
251
+ if langgraph and not LANGGRAPH_ENABLED:
252
+ raise ImportError("LangGraph is not installed. Please install it to use this feature.")
253
+
254
+ requires_authorization = (
255
+ tool_def.requirements is not None and tool_def.requirements.authorization is not None
256
+ )
257
+
258
+ async def tool_function(config: RunnableConfig, **kwargs: Any) -> Any:
259
+ """Run the Arcade tool with the given parameters.
260
+
261
+ Args:
262
+ config: RunnableConfig containing execution context.
263
+ **kwargs: Tool input arguments.
264
+
265
+ Returns:
266
+ The output from the tool execution.
267
+ """
268
+ user_id = config.get("configurable", {}).get("user_id") if config else None
269
+
270
+ if requires_authorization:
271
+ if user_id is None:
272
+ error_message = f"user_id is required to run {tool_name}"
273
+ if langgraph:
274
+ raise NodeInterrupt(error_message)
275
+ return {"error": error_message}
276
+
277
+ # Authorize the user for the tool
278
+ auth_response = await client.tools.authorize(tool_name=tool_name, user_id=user_id)
279
+ if auth_response.status != "completed":
280
+ auth_message = f"Please use the following link to authorize: {auth_response.url}"
281
+ if langgraph:
282
+ raise NodeInterrupt(auth_message)
283
+ return {"error": auth_message}
284
+
285
+ # Execute the tool with provided inputs
286
+ execute_response = await client.tools.execute(
287
+ tool_name=tool_name,
288
+ input=kwargs,
289
+ user_id=user_id if user_id is not None else NOT_GIVEN,
290
+ )
291
+
292
+ return process_tool_execution_response(execute_response, tool_name, langgraph)
293
+
294
+ return tool_function