langchain-arcade 1.0.0__tar.gz
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-1.0.0/LICENSE +21 -0
- langchain_arcade-1.0.0/PKG-INFO +58 -0
- langchain_arcade-1.0.0/README.md +38 -0
- langchain_arcade-1.0.0/langchain_arcade/__init__.py +3 -0
- langchain_arcade-1.0.0/langchain_arcade/_utilities.py +179 -0
- langchain_arcade-1.0.0/langchain_arcade/manager.py +219 -0
- langchain_arcade-1.0.0/langchain_arcade/py.typed +0 -0
- langchain_arcade-1.0.0/pyproject.toml +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Arcade AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: langchain-arcade
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An integration package connecting Arcade and LangChain/LangGraph
|
|
5
|
+
Home-page: https://github.com/arcadeai/arcade-ai/tree/main/contrib/langchain
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Arcade AI
|
|
8
|
+
Author-email: dev@arcade-ai.com
|
|
9
|
+
Requires-Python: >=3.10,<3.13
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: arcadepy (>=1.0.0,<2.0.0)
|
|
16
|
+
Requires-Dist: langgraph (>=0.2.67,<0.3.0)
|
|
17
|
+
Project-URL: Repository, https://github.com/arcadeai/arcade-ai/tree/main/contrib/langchain
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
<h3 align="center">
|
|
21
|
+
<a name="readme-top"></a>
|
|
22
|
+
<img
|
|
23
|
+
src="https://docs.arcade-ai.com/images/logo/arcade-ai-logo.png"
|
|
24
|
+
>
|
|
25
|
+
</h3>
|
|
26
|
+
<div align="center">
|
|
27
|
+
<h3>LangChain Integration</h3>
|
|
28
|
+
<a href="https://github.com/arcadeai/arcade-ai/blob/main/LICENSE">
|
|
29
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License">
|
|
30
|
+
</a>
|
|
31
|
+
<a href="https://pepy.tech/project/langchain-arcade">
|
|
32
|
+
<img src="https://static.pepy.tech/badge/langchain-arcade" alt="Downloads">
|
|
33
|
+
</a>
|
|
34
|
+
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<p align="center">
|
|
38
|
+
<a href="https://docs.arcade-ai.com" target="_blank">Docs</a> •
|
|
39
|
+
<a href="https://docs.arcade-ai.com/integrations" target="_blank">Integrations</a> •
|
|
40
|
+
<a href="https://github.com/ArcadeAI/cookbook" target="_blank">Cookbook</a> •
|
|
41
|
+
<a href="https://github.com/ArcadeAI/arcade-py" target="_blank">Python Client</a> •
|
|
42
|
+
<a href="https://github.com/ArcadeAI/arcade-js" target="_blank">JavaScript Client</a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
## Overview
|
|
46
|
+
|
|
47
|
+
`langchain-arcade` allows you to use Arcade AI tools in your LangChain and LangGraph applications.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install langchain-arcade
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
See the [examples](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/langchain) for usage examples
|
|
58
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<h3 align="center">
|
|
2
|
+
<a name="readme-top"></a>
|
|
3
|
+
<img
|
|
4
|
+
src="https://docs.arcade-ai.com/images/logo/arcade-ai-logo.png"
|
|
5
|
+
>
|
|
6
|
+
</h3>
|
|
7
|
+
<div align="center">
|
|
8
|
+
<h3>LangChain Integration</h3>
|
|
9
|
+
<a href="https://github.com/arcadeai/arcade-ai/blob/main/LICENSE">
|
|
10
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License">
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://pepy.tech/project/langchain-arcade">
|
|
13
|
+
<img src="https://static.pepy.tech/badge/langchain-arcade" alt="Downloads">
|
|
14
|
+
</a>
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://docs.arcade-ai.com" target="_blank">Docs</a> •
|
|
20
|
+
<a href="https://docs.arcade-ai.com/integrations" target="_blank">Integrations</a> •
|
|
21
|
+
<a href="https://github.com/ArcadeAI/cookbook" target="_blank">Cookbook</a> •
|
|
22
|
+
<a href="https://github.com/ArcadeAI/arcade-py" target="_blank">Python Client</a> •
|
|
23
|
+
<a href="https://github.com/ArcadeAI/arcade-js" target="_blank">JavaScript Client</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
`langchain-arcade` allows you to use Arcade AI tools in your LangChain and LangGraph applications.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install langchain-arcade
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
See the [examples](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/langchain) for usage examples
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from typing import Any, Callable
|
|
2
|
+
|
|
3
|
+
from arcadepy import NOT_GIVEN, Arcade
|
|
4
|
+
from arcadepy.types import ToolDefinition
|
|
5
|
+
from langchain_core.runnables import RunnableConfig
|
|
6
|
+
from langchain_core.tools import StructuredTool
|
|
7
|
+
from pydantic import BaseModel, Field, create_model
|
|
8
|
+
|
|
9
|
+
# Check if LangGraph is enabled
|
|
10
|
+
LANGGRAPH_ENABLED = True
|
|
11
|
+
try:
|
|
12
|
+
from langgraph.errors import NodeInterrupt
|
|
13
|
+
except ImportError:
|
|
14
|
+
LANGGRAPH_ENABLED = False
|
|
15
|
+
|
|
16
|
+
# Mapping of Arcade value types to Python types
|
|
17
|
+
TYPE_MAPPING = {
|
|
18
|
+
"string": str,
|
|
19
|
+
"number": float,
|
|
20
|
+
"integer": int,
|
|
21
|
+
"boolean": bool,
|
|
22
|
+
"array": list,
|
|
23
|
+
"json": dict,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_python_type(val_type: str) -> Any:
|
|
28
|
+
"""Map Arcade value types to Python types.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
val_type: The value type as a string.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Corresponding Python type.
|
|
35
|
+
"""
|
|
36
|
+
_type = TYPE_MAPPING.get(val_type)
|
|
37
|
+
if _type is None:
|
|
38
|
+
raise ValueError(f"Invalid value type: {val_type}")
|
|
39
|
+
return _type
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def tool_definition_to_pydantic_model(tool_def: ToolDefinition) -> type[BaseModel]:
|
|
43
|
+
"""Convert a ToolDefinition's inputs into a Pydantic BaseModel.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
tool_def: The ToolDefinition object to convert.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A Pydantic BaseModel class representing the tool's input schema.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
fields: dict[str, Any] = {}
|
|
53
|
+
for param in tool_def.input.parameters or []:
|
|
54
|
+
param_type = get_python_type(param.value_schema.val_type)
|
|
55
|
+
if param_type == list and param.value_schema.inner_val_type: # noqa: E721
|
|
56
|
+
inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type)
|
|
57
|
+
param_type = list[inner_type] # type: ignore[valid-type]
|
|
58
|
+
param_description = param.description or "No description provided."
|
|
59
|
+
default = ... if param.required else None
|
|
60
|
+
fields[param.name] = (
|
|
61
|
+
param_type,
|
|
62
|
+
Field(default=default, description=param_description),
|
|
63
|
+
)
|
|
64
|
+
return create_model(f"{tool_def.name}Args", **fields)
|
|
65
|
+
except ValueError as e:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Error converting {tool_def.name} parameters into pydantic model for langchain: {e}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def create_tool_function(
|
|
72
|
+
client: Arcade,
|
|
73
|
+
tool_name: str,
|
|
74
|
+
tool_def: ToolDefinition,
|
|
75
|
+
args_schema: type[BaseModel],
|
|
76
|
+
langgraph: bool = False,
|
|
77
|
+
) -> Callable:
|
|
78
|
+
"""Create a callable function to execute an Arcade tool.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
client: The Arcade client instance.
|
|
82
|
+
tool_name: The name of the tool to wrap.
|
|
83
|
+
tool_def: The ToolDefinition of the tool to wrap.
|
|
84
|
+
args_schema: The Pydantic model representing the tool's arguments.
|
|
85
|
+
langgraph: Whether to enable LangGraph-specific behavior.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
A callable function that executes the tool.
|
|
89
|
+
"""
|
|
90
|
+
if langgraph and not LANGGRAPH_ENABLED:
|
|
91
|
+
raise ImportError("LangGraph is not installed. Please install it to use this feature.")
|
|
92
|
+
|
|
93
|
+
requires_authorization = (
|
|
94
|
+
tool_def.requirements is not None and tool_def.requirements.authorization is not None
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def tool_function(config: RunnableConfig, **kwargs: Any) -> Any:
|
|
98
|
+
"""Execute the Arcade tool with the given parameters.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
config: RunnableConfig containing execution context.
|
|
102
|
+
**kwargs: Tool input arguments.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The output from the tool execution.
|
|
106
|
+
"""
|
|
107
|
+
user_id = config.get("configurable", {}).get("user_id") if config else None
|
|
108
|
+
|
|
109
|
+
if requires_authorization:
|
|
110
|
+
if user_id is None:
|
|
111
|
+
error_message = f"user_id is required to run {tool_name}"
|
|
112
|
+
if langgraph:
|
|
113
|
+
raise NodeInterrupt(error_message)
|
|
114
|
+
return {"error": error_message}
|
|
115
|
+
|
|
116
|
+
# Authorize the user for the tool
|
|
117
|
+
auth_response = client.tools.authorize(tool_name=tool_name, user_id=user_id)
|
|
118
|
+
if auth_response.status != "completed":
|
|
119
|
+
auth_message = f"Please use the following link to authorize: {auth_response.url}"
|
|
120
|
+
if langgraph:
|
|
121
|
+
raise NodeInterrupt(auth_message)
|
|
122
|
+
return {"error": auth_message}
|
|
123
|
+
|
|
124
|
+
# Execute the tool with provided inputs
|
|
125
|
+
execute_response = client.tools.execute(
|
|
126
|
+
tool_name=tool_name,
|
|
127
|
+
input=kwargs,
|
|
128
|
+
user_id=user_id if user_id is not None else NOT_GIVEN,
|
|
129
|
+
)
|
|
130
|
+
|
|
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}
|
|
137
|
+
|
|
138
|
+
return tool_function
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def wrap_arcade_tool(
|
|
142
|
+
client: Arcade,
|
|
143
|
+
tool_name: str,
|
|
144
|
+
tool_def: ToolDefinition,
|
|
145
|
+
langgraph: bool = False,
|
|
146
|
+
) -> StructuredTool:
|
|
147
|
+
"""Wrap an Arcade `ToolDefinition` as a LangChain `StructuredTool`.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
client: The Arcade client instance.
|
|
151
|
+
tool_name: The name of the tool to wrap.
|
|
152
|
+
tool_def: The ToolDefinition object to wrap.
|
|
153
|
+
langgraph: Whether to enable LangGraph-specific behavior.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A StructuredTool instance representing the Arcade tool.
|
|
157
|
+
"""
|
|
158
|
+
description = tool_def.description or "No description provided."
|
|
159
|
+
|
|
160
|
+
# Create a Pydantic model for the tool's input arguments
|
|
161
|
+
args_schema = tool_definition_to_pydantic_model(tool_def)
|
|
162
|
+
|
|
163
|
+
# 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
|
+
)
|
|
171
|
+
|
|
172
|
+
# Create the StructuredTool instance
|
|
173
|
+
return StructuredTool.from_function(
|
|
174
|
+
func=action_func,
|
|
175
|
+
name=tool_name,
|
|
176
|
+
description=description,
|
|
177
|
+
args_schema=args_schema,
|
|
178
|
+
inject_kwargs={"user_id"},
|
|
179
|
+
)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from arcadepy import Arcade
|
|
6
|
+
from arcadepy.types import ToolDefinition
|
|
7
|
+
from arcadepy.types.shared import AuthorizationResponse
|
|
8
|
+
from langchain_core.tools import StructuredTool
|
|
9
|
+
|
|
10
|
+
from langchain_arcade._utilities import (
|
|
11
|
+
wrap_arcade_tool,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArcadeToolManager:
|
|
16
|
+
"""
|
|
17
|
+
Arcade tool manager for LangChain framework.
|
|
18
|
+
|
|
19
|
+
This class wraps Arcade tools as LangChain `StructuredTool`
|
|
20
|
+
objects for integration.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
client: Optional[Arcade] = None,
|
|
26
|
+
**kwargs: dict[str, Any],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Initialize the ArcadeToolManager.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
32
|
+
>>>
|
|
33
|
+
>>> # retrieve a specific tool as a langchain tool
|
|
34
|
+
>>> manager.get_tools(tools=["Search.SearchGoogle"])
|
|
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"])
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
client: Optional Arcade client instance.
|
|
44
|
+
**kwargs: Additional keyword arguments to pass to the Arcade client.
|
|
45
|
+
"""
|
|
46
|
+
if not client:
|
|
47
|
+
api_key = kwargs.get("api_key", os.getenv("ARCADE_API_KEY"))
|
|
48
|
+
base_url = kwargs.get("base_url", os.getenv("ARCADE_BASE_URL"))
|
|
49
|
+
arcade_kwargs = {"api_key": api_key, **kwargs}
|
|
50
|
+
if base_url:
|
|
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] = {}
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def tools(self) -> list[str]:
|
|
59
|
+
return list(self._tools.keys())
|
|
60
|
+
|
|
61
|
+
def __iter__(self) -> Iterator[tuple[str, ToolDefinition]]:
|
|
62
|
+
yield from self._tools.items()
|
|
63
|
+
|
|
64
|
+
def __len__(self) -> int:
|
|
65
|
+
return len(self._tools)
|
|
66
|
+
|
|
67
|
+
def __getitem__(self, tool_name: str) -> ToolDefinition:
|
|
68
|
+
return self._tools[tool_name]
|
|
69
|
+
|
|
70
|
+
def init_tools(
|
|
71
|
+
self,
|
|
72
|
+
tools: Optional[list[str]] = None,
|
|
73
|
+
toolkits: Optional[list[str]] = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Initialize the tools in the manager.
|
|
76
|
+
|
|
77
|
+
This will clear any existing tools in the manager.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
81
|
+
>>> manager.init_tools(tools=["Search.SearchGoogle"])
|
|
82
|
+
>>> manager.get_tools()
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
tools: Optional list of tool names to include.
|
|
86
|
+
toolkits: Optional list of toolkits to include.
|
|
87
|
+
"""
|
|
88
|
+
self._tools = self._retrieve_tool_definitions(tools, toolkits)
|
|
89
|
+
|
|
90
|
+
def get_tools(
|
|
91
|
+
self,
|
|
92
|
+
tools: Optional[list[str]] = None,
|
|
93
|
+
toolkits: Optional[list[str]] = None,
|
|
94
|
+
langgraph: bool = True,
|
|
95
|
+
) -> list[StructuredTool]:
|
|
96
|
+
"""Return the tools in the manager as LangChain StructuredTool objects.
|
|
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.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
107
|
+
>>>
|
|
108
|
+
>>> # retrieve a specific tool as a langchain tool
|
|
109
|
+
>>> manager.get_tools(tools=["Search.SearchGoogle"])
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
tools: Optional list of tool names to include.
|
|
113
|
+
toolkits: Optional list of toolkits to include.
|
|
114
|
+
langgraph: Whether to use LangGraph-specific behavior
|
|
115
|
+
such as NodeInterrupts for auth.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of StructuredTool instances.
|
|
119
|
+
"""
|
|
120
|
+
# TODO account for versioning
|
|
121
|
+
if tools or toolkits:
|
|
122
|
+
new_tools = self._retrieve_tool_definitions(tools, toolkits)
|
|
123
|
+
self._tools.update(new_tools)
|
|
124
|
+
elif len(self) == 0:
|
|
125
|
+
self.init_tools()
|
|
126
|
+
|
|
127
|
+
langchain_tools: list[StructuredTool] = []
|
|
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
|
|
132
|
+
|
|
133
|
+
def authorize(self, tool_name: str, user_id: str) -> AuthorizationResponse:
|
|
134
|
+
"""Authorize a user for a tool.
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
138
|
+
>>> manager.authorize("X.PostTweet", "user_123")
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
tool_name: The name of the tool to authorize.
|
|
142
|
+
user_id: The user ID to authorize.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
AuthorizationResponse
|
|
146
|
+
"""
|
|
147
|
+
return self.client.tools.authorize(tool_name=tool_name, user_id=user_id)
|
|
148
|
+
|
|
149
|
+
def is_authorized(self, authorization_id: str) -> bool:
|
|
150
|
+
"""Check if a tool authorization is complete.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
154
|
+
>>> manager.init_tools(toolkits=["Search"])
|
|
155
|
+
>>> manager.is_authorized("auth_123")
|
|
156
|
+
"""
|
|
157
|
+
return self.client.auth.status(id=authorization_id).status == "completed"
|
|
158
|
+
|
|
159
|
+
def wait_for_auth(self, authorization_id: str) -> AuthorizationResponse:
|
|
160
|
+
"""Wait for a tool authorization to complete.
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
>>> manager = ArcadeToolManager(api_key="...")
|
|
164
|
+
>>> manager.init_tools(toolkits=["Google.ListEmails"])
|
|
165
|
+
>>> response = manager.authorize("Google.ListEmails", "user_123")
|
|
166
|
+
>>> manager.wait_for_auth(response)
|
|
167
|
+
>>> # or
|
|
168
|
+
>>> manager.wait_for_auth(response.id)
|
|
169
|
+
"""
|
|
170
|
+
return self.client.auth.wait_for_completion(authorization_id)
|
|
171
|
+
|
|
172
|
+
def requires_auth(self, tool_name: str) -> bool:
|
|
173
|
+
"""Check if a tool requires authorization."""
|
|
174
|
+
|
|
175
|
+
tool_def = self._get_tool_definition(tool_name)
|
|
176
|
+
if tool_def.requirements is None:
|
|
177
|
+
return False
|
|
178
|
+
return tool_def.requirements.authorization is not None
|
|
179
|
+
|
|
180
|
+
def _get_tool_definition(self, tool_name: str) -> ToolDefinition:
|
|
181
|
+
try:
|
|
182
|
+
return self._tools[tool_name]
|
|
183
|
+
except KeyError:
|
|
184
|
+
raise ValueError(f"Tool '{tool_name}' not found in this ArcadeToolManager instance")
|
|
185
|
+
|
|
186
|
+
def _retrieve_tool_definitions(
|
|
187
|
+
self, tools: Optional[list[str]] = None, toolkits: Optional[list[str]] = None
|
|
188
|
+
) -> dict[str, ToolDefinition]:
|
|
189
|
+
"""Retrieve tool definitions from the Arcade client, accounting for pagination."""
|
|
190
|
+
all_tools: list[ToolDefinition] = []
|
|
191
|
+
|
|
192
|
+
# First, gather single tools if the user specifically requested them.
|
|
193
|
+
if tools:
|
|
194
|
+
for tool_id in tools:
|
|
195
|
+
# ToolsResource.get(...) returns a single ToolDefinition.
|
|
196
|
+
single_tool = self.client.tools.get(name=tool_id)
|
|
197
|
+
all_tools.append(single_tool)
|
|
198
|
+
|
|
199
|
+
# Next, gather tool definitions from any requested toolkits.
|
|
200
|
+
if toolkits:
|
|
201
|
+
for tk in toolkits:
|
|
202
|
+
# tools.list(...) returns a paginated response (SyncOffsetPage),
|
|
203
|
+
# so we iterate over its items to accumulate tool definitions.
|
|
204
|
+
paginated_tools = self.client.tools.list(toolkit=tk)
|
|
205
|
+
all_tools.extend(paginated_tools.items)
|
|
206
|
+
|
|
207
|
+
# If no specific tools or toolkits were requested, retrieve *all* tools.
|
|
208
|
+
if not tools and not toolkits:
|
|
209
|
+
paginated_all_tools = self.client.tools.list()
|
|
210
|
+
all_tools.extend(paginated_all_tools.items)
|
|
211
|
+
# Build a dictionary that maps the "full_tool_name" to the tool definition.
|
|
212
|
+
tool_definitions: dict[str, ToolDefinition] = {}
|
|
213
|
+
for tool in all_tools:
|
|
214
|
+
# For items returned by .list(), the 'toolkit' and 'name' attributes
|
|
215
|
+
# should be present as plain fields on the object. (No need to do toolkit.name)
|
|
216
|
+
full_tool_name = f"{tool.toolkit.name}_{tool.name}"
|
|
217
|
+
tool_definitions[full_tool_name] = tool
|
|
218
|
+
|
|
219
|
+
return tool_definitions
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "langchain-arcade"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "An integration package connecting Arcade and LangChain/LangGraph"
|
|
5
|
+
authors = ["Arcade AI <dev@arcade-ai.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
repository = "https://github.com/arcadeai/arcade-ai/tree/main/contrib/langchain"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">=3.10,<3.13"
|
|
12
|
+
arcadepy = "^1.0.0"
|
|
13
|
+
langgraph = ">=0.2.67,<0.3.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
pytest = "^8.1.2"
|
|
18
|
+
pytest-cov = "^4.0.0"
|
|
19
|
+
mypy = "^1.5.1"
|
|
20
|
+
pre-commit = "^3.4.0"
|
|
21
|
+
tox = "^4.11.1"
|
|
22
|
+
pytest-asyncio = "^0.23.7"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
[tool.mypy]
|
|
26
|
+
files = ["langchain_arcade"]
|
|
27
|
+
python_version = "3.10"
|
|
28
|
+
disallow_untyped_defs = "True"
|
|
29
|
+
disallow_any_unimported = "True"
|
|
30
|
+
no_implicit_optional = "True"
|
|
31
|
+
check_untyped_defs = "True"
|
|
32
|
+
warn_return_any = "True"
|
|
33
|
+
warn_unused_ignores = "True"
|
|
34
|
+
show_error_codes = "True"
|
|
35
|
+
ignore_missing_imports = "True"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
[tool.coverage.run]
|
|
42
|
+
branch = true
|
|
43
|
+
source = ["langchain_arcade"]
|
|
44
|
+
|
|
45
|
+
[tool.coverage.report]
|
|
46
|
+
skip_empty = true
|