glaip-sdk 0.5.3__py3-none-any.whl → 0.6.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.
- glaip_sdk/__init__.py +4 -1
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +989 -0
- glaip_sdk/cli/commands/accounts.py +210 -23
- glaip_sdk/cli/commands/tools.py +2 -5
- glaip_sdk/client/_agent_payloads.py +10 -9
- glaip_sdk/client/agents.py +70 -8
- glaip_sdk/client/base.py +1 -0
- glaip_sdk/client/main.py +12 -4
- glaip_sdk/client/mcps.py +112 -10
- glaip_sdk/client/tools.py +151 -7
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +65 -31
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +0 -1
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +251 -0
- glaip_sdk/registry/tool.py +238 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +50 -9
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +26 -7
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/import_resolver.py +500 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/sync.py +142 -0
- {glaip_sdk-0.5.3.dist-info → glaip_sdk-0.6.0.dist-info}/METADATA +5 -3
- {glaip_sdk-0.5.3.dist-info → glaip_sdk-0.6.0.dist-info}/RECORD +38 -18
- glaip_sdk/models.py +0 -241
- {glaip_sdk-0.5.3.dist-info → glaip_sdk-0.6.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.5.3.dist-info → glaip_sdk-0.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Tool registry for glaip_sdk.
|
|
2
|
+
|
|
3
|
+
This module provides the ToolRegistry that caches deployed tools
|
|
4
|
+
to avoid redundant API calls when deploying agents with tools.
|
|
5
|
+
|
|
6
|
+
Authors:
|
|
7
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.registry.base import BaseRegistry
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from glaip_sdk.tools import Tool
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToolRegistry(BaseRegistry["Tool"]):
|
|
24
|
+
"""Registry for tools.
|
|
25
|
+
|
|
26
|
+
Resolves tool references to glaip_sdk.models.Tool objects.
|
|
27
|
+
Caches results to avoid redundant API calls and duplicate uploads.
|
|
28
|
+
|
|
29
|
+
Handles:
|
|
30
|
+
- Tool classes (LangChain BaseTool subclasses) → upload, cache, return Tool
|
|
31
|
+
- glaip_sdk.models.Tool → return as-is (uses tool.id)
|
|
32
|
+
- String names → lookup on platform, cache, return Tool
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
_cache: Internal cache mapping names to Tool objects.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> registry = get_tool_registry()
|
|
39
|
+
>>> tool = registry.resolve(WebSearchTool)
|
|
40
|
+
>>> print(tool.id)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def _get_name_from_model_fields(self, ref: type) -> str | None:
|
|
44
|
+
"""Extract name from Pydantic model_fields if available."""
|
|
45
|
+
model_fields = getattr(ref, "model_fields", {})
|
|
46
|
+
if "name" not in model_fields:
|
|
47
|
+
return None
|
|
48
|
+
field_info = model_fields["name"]
|
|
49
|
+
default = getattr(field_info, "default", None)
|
|
50
|
+
return default if isinstance(default, str) else None
|
|
51
|
+
|
|
52
|
+
def _get_string_attr(self, obj: Any, attr: str) -> str | None:
|
|
53
|
+
"""Get attribute if it's a string, otherwise None."""
|
|
54
|
+
value = getattr(obj, attr, None)
|
|
55
|
+
return value if isinstance(value, str) else None
|
|
56
|
+
|
|
57
|
+
def _extract_name(self, ref: Any) -> str:
|
|
58
|
+
"""Extract tool name from a reference.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ref: A tool class, instance, dict, or string name.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The extracted tool name.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ValueError: If name cannot be extracted from the reference.
|
|
68
|
+
"""
|
|
69
|
+
if isinstance(ref, str):
|
|
70
|
+
return ref
|
|
71
|
+
|
|
72
|
+
# Dict from API response - extract name or id
|
|
73
|
+
if isinstance(ref, dict):
|
|
74
|
+
return ref.get("name") or ref.get("id") or ""
|
|
75
|
+
|
|
76
|
+
# Tool instance (not a class) with name attribute
|
|
77
|
+
if not isinstance(ref, type):
|
|
78
|
+
name = self._get_string_attr(ref, "name")
|
|
79
|
+
if name:
|
|
80
|
+
return name
|
|
81
|
+
|
|
82
|
+
# Tool class - try direct attribute first, then model_fields
|
|
83
|
+
if isinstance(ref, type):
|
|
84
|
+
name = self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
|
|
85
|
+
if name:
|
|
86
|
+
return name
|
|
87
|
+
|
|
88
|
+
raise ValueError(f"Cannot extract name from: {ref}")
|
|
89
|
+
|
|
90
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
|
|
91
|
+
"""Resolve tool reference - upload if class, find if string/native.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
ref: The tool reference to resolve.
|
|
95
|
+
name: The extracted tool name.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The resolved glaip_sdk.models.Tool object.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If the tool cannot be resolved.
|
|
102
|
+
"""
|
|
103
|
+
# Lazy imports to avoid circular dependency
|
|
104
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
105
|
+
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
106
|
+
|
|
107
|
+
# Already deployed tool (glaip_sdk.models.Tool with ID) - just cache and return
|
|
108
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
109
|
+
if ref.id is not None:
|
|
110
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
111
|
+
self._cache[name] = ref
|
|
112
|
+
return ref
|
|
113
|
+
|
|
114
|
+
# Tool without ID (e.g., Tool.from_native()) - look up on platform
|
|
115
|
+
logger.info("Looking up native tool: %s", name)
|
|
116
|
+
tool = find_tool(name)
|
|
117
|
+
if tool:
|
|
118
|
+
self._cache[name] = tool
|
|
119
|
+
return tool
|
|
120
|
+
raise ValueError(f"Native tool not found on platform: {name}")
|
|
121
|
+
|
|
122
|
+
# Custom tool class - upload it
|
|
123
|
+
if self._is_custom_tool(ref):
|
|
124
|
+
logger.info("Uploading custom tool: %s", name)
|
|
125
|
+
tool = update_or_create_tool(ref)
|
|
126
|
+
self._cache[name] = tool
|
|
127
|
+
if tool.id:
|
|
128
|
+
self._cache[tool.id] = tool
|
|
129
|
+
return tool
|
|
130
|
+
|
|
131
|
+
# Dict from API response - use ID directly if available
|
|
132
|
+
if isinstance(ref, dict):
|
|
133
|
+
tool_id = ref.get("id")
|
|
134
|
+
if tool_id:
|
|
135
|
+
from glaip_sdk.tools.base import Tool # noqa: PLC0415
|
|
136
|
+
|
|
137
|
+
tool = Tool(id=tool_id, name=ref.get("name", ""))
|
|
138
|
+
self._cache[name] = tool
|
|
139
|
+
return tool
|
|
140
|
+
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
141
|
+
|
|
142
|
+
# String name - look up on platform (could be native or existing tool)
|
|
143
|
+
if isinstance(ref, str):
|
|
144
|
+
logger.info("Looking up tool by name: %s", name)
|
|
145
|
+
tool = find_tool(name)
|
|
146
|
+
if tool:
|
|
147
|
+
self._cache[name] = tool
|
|
148
|
+
return tool
|
|
149
|
+
raise ValueError(f"Tool not found on platform: {name}")
|
|
150
|
+
|
|
151
|
+
raise ValueError(f"Could not resolve tool reference: {ref}")
|
|
152
|
+
|
|
153
|
+
def _is_custom_tool(self, ref: Any) -> bool:
|
|
154
|
+
"""Check if reference is a custom tool class/instance.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ref: The reference to check.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if ref is a custom tool that needs uploading.
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
from langchain_core.tools import BaseTool # noqa: PLC0415
|
|
164
|
+
|
|
165
|
+
# LangChain BaseTool class
|
|
166
|
+
if isinstance(ref, type) and issubclass(ref, BaseTool):
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
# LangChain BaseTool instance
|
|
170
|
+
if isinstance(ref, BaseTool):
|
|
171
|
+
return True
|
|
172
|
+
except ImportError:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
def resolve(self, ref: Any) -> Tool:
|
|
178
|
+
"""Resolve a tool reference to a platform Tool object.
|
|
179
|
+
|
|
180
|
+
Overrides base resolve to handle SDK tools differently.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
ref: The tool reference to resolve.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
The resolved glaip_sdk.models.Tool object.
|
|
187
|
+
"""
|
|
188
|
+
# Check if it's a Tool instance (not a class)
|
|
189
|
+
if hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type):
|
|
190
|
+
# If Tool has an ID, it's already deployed - return as-is
|
|
191
|
+
if ref.id is not None:
|
|
192
|
+
name = self._extract_name(ref)
|
|
193
|
+
if name not in self._cache:
|
|
194
|
+
self._cache[name] = ref
|
|
195
|
+
return ref
|
|
196
|
+
|
|
197
|
+
# Tool without ID (e.g., from Tool.from_native()) - needs platform lookup
|
|
198
|
+
# Fall through to normal resolution
|
|
199
|
+
|
|
200
|
+
return super().resolve(ref)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class _ToolRegistrySingleton:
|
|
204
|
+
"""Singleton holder for ToolRegistry to avoid global statement."""
|
|
205
|
+
|
|
206
|
+
_instance: ToolRegistry | None = None
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def get_instance(cls) -> ToolRegistry:
|
|
210
|
+
"""Get or create the singleton instance.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
The global ToolRegistry instance.
|
|
214
|
+
"""
|
|
215
|
+
if cls._instance is None:
|
|
216
|
+
cls._instance = ToolRegistry()
|
|
217
|
+
return cls._instance
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def reset(cls) -> None:
|
|
221
|
+
"""Reset the singleton instance (for testing)."""
|
|
222
|
+
cls._instance = None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def get_tool_registry() -> ToolRegistry:
|
|
226
|
+
"""Get the singleton ToolRegistry instance.
|
|
227
|
+
|
|
228
|
+
Returns a global ToolRegistry that caches tools across the session.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
The global ToolRegistry instance.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
>>> from glaip_sdk.registry import get_tool_registry
|
|
235
|
+
>>> registry = get_tool_registry()
|
|
236
|
+
>>> tool = registry.resolve("web_search")
|
|
237
|
+
"""
|
|
238
|
+
return _ToolRegistrySingleton.get_instance()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Tool package for GL AIP platform.
|
|
2
|
+
|
|
3
|
+
This package provides the Tool class, ToolType enum, and ToolRegistry
|
|
4
|
+
for managing tools on the GL AIP platform.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from glaip_sdk.tools import Tool, ToolType, get_tool_registry
|
|
8
|
+
>>> native_tool = Tool.from_native("web_search")
|
|
9
|
+
>>> custom_tool = Tool.from_langchain(MyCustomTool)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from glaip_sdk.registry.tool import ToolRegistry, get_tool_registry
|
|
15
|
+
from glaip_sdk.tools.base import Tool, ToolType
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Tool",
|
|
19
|
+
"ToolType",
|
|
20
|
+
"ToolRegistry",
|
|
21
|
+
"get_tool_registry",
|
|
22
|
+
]
|
glaip_sdk/tools/base.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""Tool class for lazy tool references.
|
|
2
|
+
|
|
3
|
+
This module provides the Tool class that serves as a lazy reference
|
|
4
|
+
to tools on the GL AIP platform. Tools are only resolved when
|
|
5
|
+
Agent.deploy() is called.
|
|
6
|
+
|
|
7
|
+
The Tool class also supports runtime operations (update, delete, get_script)
|
|
8
|
+
when retrieved from the API via client.tools.get().
|
|
9
|
+
|
|
10
|
+
Authors:
|
|
11
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
12
|
+
|
|
13
|
+
Example - Lazy Reference:
|
|
14
|
+
>>> from glaip_sdk.tools import Tool
|
|
15
|
+
>>>
|
|
16
|
+
>>> # Reference a native platform tool
|
|
17
|
+
>>> time_tool = Tool.from_native("time_tool")
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Reference a custom LangChain tool
|
|
20
|
+
>>> greeting_tool = Tool.from_langchain(GreetingTool)
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Use in an agent
|
|
23
|
+
>>> class MyAgent(Agent):
|
|
24
|
+
... @property
|
|
25
|
+
... def tools(self) -> list:
|
|
26
|
+
... return [time_tool, greeting_tool]
|
|
27
|
+
|
|
28
|
+
Example - Runtime Operations:
|
|
29
|
+
>>> from glaip_sdk import Glaip
|
|
30
|
+
>>>
|
|
31
|
+
>>> client = Glaip()
|
|
32
|
+
>>> tool = client.tools.get("tool-123")
|
|
33
|
+
>>> script = tool.get_script() # Get tool script content
|
|
34
|
+
>>> tool.update(description="Updated description")
|
|
35
|
+
>>> tool.delete()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from enum import StrEnum
|
|
41
|
+
from typing import TYPE_CHECKING, Any
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from glaip_sdk.models import ToolResponse
|
|
45
|
+
|
|
46
|
+
_TOOL_NOT_DEPLOYED_MSG = "Tool not available on platform. No ID set."
|
|
47
|
+
_CLIENT_NOT_AVAILABLE_MSG = "Client not available. Use client.tools.get() to get a client-connected tool."
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ToolType(StrEnum):
|
|
51
|
+
"""Type of tool reference."""
|
|
52
|
+
|
|
53
|
+
NATIVE = "native"
|
|
54
|
+
CUSTOM = "custom"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Tool:
|
|
58
|
+
"""Tool class for GL AIP platform.
|
|
59
|
+
|
|
60
|
+
Supports both lazy references and runtime operations:
|
|
61
|
+
- Lazy reference: Created via from_native() or from_langchain()
|
|
62
|
+
- Runtime: Created via from_response() or client.tools.get()
|
|
63
|
+
|
|
64
|
+
Use factory methods to create Tool instances:
|
|
65
|
+
- Tool.from_native(name) - Reference a native platform tool
|
|
66
|
+
- Tool.from_langchain(tool_class) - Reference a custom LangChain tool
|
|
67
|
+
- Tool.from_response(response, client) - From API response
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
name: Tool name (for native tools) or from tool_class.
|
|
71
|
+
id: Tool ID on the platform (set after deployment or from API).
|
|
72
|
+
tool_class: LangChain BaseTool class (for custom tools) or None.
|
|
73
|
+
tool_type: Type of tool (native or custom).
|
|
74
|
+
description: Tool description (from API response).
|
|
75
|
+
tool_script: Tool script content (from API response).
|
|
76
|
+
|
|
77
|
+
Example - Lazy Reference:
|
|
78
|
+
>>> # Native tool
|
|
79
|
+
>>> time_tool = Tool.from_native("time_tool")
|
|
80
|
+
>>>
|
|
81
|
+
>>> # Custom tool
|
|
82
|
+
>>> greeting_tool = Tool.from_langchain(GreetingTool)
|
|
83
|
+
|
|
84
|
+
Example - Runtime Operations:
|
|
85
|
+
>>> tool = client.tools.get("tool-123")
|
|
86
|
+
>>> tool.update(description="New description")
|
|
87
|
+
>>> tool.delete()
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
name: str | None = None,
|
|
93
|
+
tool_class: type | None = None,
|
|
94
|
+
tool_type: str | ToolType | None = None,
|
|
95
|
+
*,
|
|
96
|
+
id: str | None = None, # noqa: A002 - Allow shadowing builtin for API compat
|
|
97
|
+
description: str | None = None,
|
|
98
|
+
tool_script: str | None = None,
|
|
99
|
+
tool_file: str | None = None,
|
|
100
|
+
framework: str | None = None,
|
|
101
|
+
version: str | None = None,
|
|
102
|
+
tags: str | list[str] | None = None,
|
|
103
|
+
type: (str | ToolType | None) = None, # noqa: A002 - Backward compat alias for tool_type
|
|
104
|
+
_client: Any = None,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Initialize a Tool.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
name: Tool name (for native tools).
|
|
110
|
+
tool_class: LangChain BaseTool class (for custom tools).
|
|
111
|
+
tool_type: Type of tool (native or custom). Accepts str or ToolType.
|
|
112
|
+
id: Tool ID on the platform.
|
|
113
|
+
description: Tool description.
|
|
114
|
+
tool_script: Tool script content.
|
|
115
|
+
tool_file: Tool file path.
|
|
116
|
+
framework: Tool framework.
|
|
117
|
+
version: Tool version.
|
|
118
|
+
tags: Tool tags.
|
|
119
|
+
type: Backward compatibility alias for tool_type.
|
|
120
|
+
_client: Internal client reference.
|
|
121
|
+
"""
|
|
122
|
+
self.name = name
|
|
123
|
+
self.tool_class = tool_class
|
|
124
|
+
# Use type as alias for tool_type (backward compatibility)
|
|
125
|
+
effective_type = tool_type if tool_type is not None else type
|
|
126
|
+
if effective_type is None:
|
|
127
|
+
effective_type = ToolType.NATIVE
|
|
128
|
+
# Normalize type to ToolType enum
|
|
129
|
+
if isinstance(effective_type, str):
|
|
130
|
+
self._type = ToolType(effective_type) if effective_type in ToolType.__members__.values() else effective_type
|
|
131
|
+
else:
|
|
132
|
+
self._type = effective_type
|
|
133
|
+
self._id = id
|
|
134
|
+
self.description = description
|
|
135
|
+
self.tool_script = tool_script
|
|
136
|
+
self.tool_file = tool_file
|
|
137
|
+
self.framework = framework
|
|
138
|
+
self.version = version
|
|
139
|
+
self.tags = tags
|
|
140
|
+
self._client = _client
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def tool_type(self) -> str | ToolType:
|
|
144
|
+
"""Tool type (native or custom)."""
|
|
145
|
+
return self._type
|
|
146
|
+
|
|
147
|
+
@tool_type.setter
|
|
148
|
+
def tool_type(self, value: str | ToolType) -> None:
|
|
149
|
+
"""Set the tool type."""
|
|
150
|
+
if isinstance(value, str):
|
|
151
|
+
self._type = ToolType(value) if value in ToolType.__members__.values() else value
|
|
152
|
+
else:
|
|
153
|
+
self._type = value
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def type(
|
|
157
|
+
self,
|
|
158
|
+
) -> str | ToolType: # noqa: A003 - Allow shadowing builtin for API compat
|
|
159
|
+
"""Tool type (native or custom). Alias for 'tool_type' for backward compatibility."""
|
|
160
|
+
return self._type
|
|
161
|
+
|
|
162
|
+
@type.setter
|
|
163
|
+
def type(self, value: str | ToolType) -> None: # noqa: A003
|
|
164
|
+
"""Set the tool type. Alias for 'tool_type' for backward compatibility."""
|
|
165
|
+
self.tool_type = value
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def id(self) -> str | None: # noqa: A003 - Allow shadowing builtin for API compat
|
|
169
|
+
"""Tool ID on the platform."""
|
|
170
|
+
return self._id
|
|
171
|
+
|
|
172
|
+
@id.setter
|
|
173
|
+
def id(self, value: str | None) -> None: # noqa: A003
|
|
174
|
+
"""Set the tool ID."""
|
|
175
|
+
self._id = value
|
|
176
|
+
|
|
177
|
+
def __repr__(self) -> str:
|
|
178
|
+
"""Return string representation."""
|
|
179
|
+
if self._id:
|
|
180
|
+
return f"Tool(id={self._id!r}, name={self.name!r})"
|
|
181
|
+
if self.type == ToolType.NATIVE:
|
|
182
|
+
return f"Tool.from_native({self.name!r})"
|
|
183
|
+
if self.tool_class is not None:
|
|
184
|
+
return f"Tool.from_langchain({self.tool_class.__name__})"
|
|
185
|
+
return f"Tool(name={self.name!r}, type={self.type})"
|
|
186
|
+
|
|
187
|
+
def __eq__(self, other: object) -> bool:
|
|
188
|
+
"""Check equality based on id if available, else name and type."""
|
|
189
|
+
if not isinstance(other, Tool):
|
|
190
|
+
return NotImplemented
|
|
191
|
+
if self._id and other._id:
|
|
192
|
+
return self._id == other._id
|
|
193
|
+
return self.name == other.name and self.type == other.type
|
|
194
|
+
|
|
195
|
+
def __hash__(self) -> int:
|
|
196
|
+
"""Hash based on id if available, else name and type."""
|
|
197
|
+
if self._id:
|
|
198
|
+
return hash(self._id)
|
|
199
|
+
return hash((self.name, self.type))
|
|
200
|
+
|
|
201
|
+
def model_dump(self, *, exclude_none: bool = False) -> dict[str, Any]:
|
|
202
|
+
"""Return a dict representation of the Tool.
|
|
203
|
+
|
|
204
|
+
Provides Pydantic-style serialization for backward compatibility.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
exclude_none: If True, exclude None values from the output.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dictionary containing Tool attributes.
|
|
211
|
+
"""
|
|
212
|
+
data = {
|
|
213
|
+
"id": self._id,
|
|
214
|
+
"name": self.name,
|
|
215
|
+
"type": str(self.type) if self.type else None,
|
|
216
|
+
"description": self.description,
|
|
217
|
+
"tool_script": self.tool_script,
|
|
218
|
+
"tool_file": self.tool_file,
|
|
219
|
+
"framework": self.framework,
|
|
220
|
+
"version": self.version,
|
|
221
|
+
"tags": self.tags,
|
|
222
|
+
}
|
|
223
|
+
if exclude_none:
|
|
224
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
225
|
+
return data
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_native(cls, name: str) -> Tool:
|
|
229
|
+
"""Create a reference to a native platform tool.
|
|
230
|
+
|
|
231
|
+
Native tools are pre-existing tools on the GL AIP platform
|
|
232
|
+
that don't require uploading (e.g., "time_tool", "web_search").
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
name: The name of the native tool on the platform.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
A Tool reference that will be resolved during Agent.deploy().
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> time_tool = Tool.from_native("time_tool")
|
|
242
|
+
>>> web_search = Tool.from_native("web_search")
|
|
243
|
+
"""
|
|
244
|
+
return cls(name=name, type=ToolType.NATIVE)
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def from_langchain(cls, tool_class: type) -> Tool:
|
|
248
|
+
"""Create a reference to a custom LangChain tool.
|
|
249
|
+
|
|
250
|
+
Custom tools are user-defined LangChain BaseTool subclasses
|
|
251
|
+
that will be uploaded to the platform during deployment.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
tool_class: A LangChain BaseTool subclass.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
A Tool reference that will be uploaded during Agent.deploy().
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
>>> from langchain_core.tools import BaseTool
|
|
261
|
+
>>>
|
|
262
|
+
>>> class GreetingTool(BaseTool):
|
|
263
|
+
... name: str = "greeting_tool"
|
|
264
|
+
... description: str = "Greets the user"
|
|
265
|
+
... def _run(self, name: str) -> str:
|
|
266
|
+
... return f"Hello, {name}!"
|
|
267
|
+
>>>
|
|
268
|
+
>>> greeting_tool = Tool.from_langchain(GreetingTool)
|
|
269
|
+
"""
|
|
270
|
+
return cls(tool_class=tool_class, type=ToolType.CUSTOM)
|
|
271
|
+
|
|
272
|
+
def get_import_path(self) -> str | None:
|
|
273
|
+
"""Get the import path for custom tools.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Import path string for custom tools, None for native tools.
|
|
277
|
+
"""
|
|
278
|
+
if self.tool_class is None:
|
|
279
|
+
return None
|
|
280
|
+
return f"{self.tool_class.__module__}.{self.tool_class.__name__}"
|
|
281
|
+
|
|
282
|
+
def get_name(self) -> str:
|
|
283
|
+
"""Get the tool name.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
The tool name (from name attribute or tool_class).
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
ValueError: If name cannot be determined.
|
|
290
|
+
"""
|
|
291
|
+
if self.name is not None:
|
|
292
|
+
return self.name
|
|
293
|
+
|
|
294
|
+
if self.tool_class is not None:
|
|
295
|
+
# LangChain BaseTool - get name from model_fields
|
|
296
|
+
if hasattr(self.tool_class, "model_fields"):
|
|
297
|
+
name_field = self.tool_class.model_fields.get("name")
|
|
298
|
+
if name_field and name_field.default:
|
|
299
|
+
return name_field.default
|
|
300
|
+
|
|
301
|
+
# Direct name attribute
|
|
302
|
+
if hasattr(self.tool_class, "name"):
|
|
303
|
+
return self.tool_class.name
|
|
304
|
+
|
|
305
|
+
raise ValueError(f"Cannot determine name for tool: {self}")
|
|
306
|
+
|
|
307
|
+
# ─────────────────────────────────────────────────────────────────
|
|
308
|
+
# Runtime Methods (require client connection)
|
|
309
|
+
# ─────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
def _set_client(self, client: Any) -> Tool:
|
|
312
|
+
"""Set the client reference for this tool.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
client: The Glaip client instance.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Self for method chaining.
|
|
319
|
+
"""
|
|
320
|
+
self._client = client
|
|
321
|
+
return self
|
|
322
|
+
|
|
323
|
+
def get_script(self) -> str:
|
|
324
|
+
"""Get the tool script content.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
The tool script content, or a placeholder message.
|
|
328
|
+
"""
|
|
329
|
+
if self.tool_script:
|
|
330
|
+
return self.tool_script
|
|
331
|
+
elif self.tool_file:
|
|
332
|
+
return f"Script content from file: {self.tool_file}"
|
|
333
|
+
else:
|
|
334
|
+
return "No script content available"
|
|
335
|
+
|
|
336
|
+
def update(self, **kwargs: Any) -> Tool:
|
|
337
|
+
"""Update the tool with new configuration.
|
|
338
|
+
|
|
339
|
+
Supports both metadata updates and file uploads.
|
|
340
|
+
Pass 'file' parameter to update tool code via file upload.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
**kwargs: Tool properties to update (name, description, etc.).
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Self with updated properties.
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
ValueError: If the tool has no ID.
|
|
350
|
+
RuntimeError: If client is not available.
|
|
351
|
+
"""
|
|
352
|
+
if not self._id:
|
|
353
|
+
raise ValueError(_TOOL_NOT_DEPLOYED_MSG)
|
|
354
|
+
if not self._client:
|
|
355
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
356
|
+
|
|
357
|
+
# Check if file upload is requested
|
|
358
|
+
if "file" in kwargs:
|
|
359
|
+
file_path = kwargs.pop("file")
|
|
360
|
+
response = self._client.tools.update_via_file(self._id, file_path, **kwargs)
|
|
361
|
+
else:
|
|
362
|
+
response = self._client.tools.update(tool_id=self._id, **kwargs)
|
|
363
|
+
|
|
364
|
+
# Update local properties from response
|
|
365
|
+
if hasattr(response, "name") and response.name:
|
|
366
|
+
self.name = response.name
|
|
367
|
+
if hasattr(response, "description"):
|
|
368
|
+
self.description = response.description
|
|
369
|
+
if hasattr(response, "tool_script"):
|
|
370
|
+
self.tool_script = response.tool_script
|
|
371
|
+
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def delete(self) -> None:
|
|
375
|
+
"""Delete the tool from the platform.
|
|
376
|
+
|
|
377
|
+
Raises:
|
|
378
|
+
ValueError: If the tool has no ID.
|
|
379
|
+
RuntimeError: If client is not available.
|
|
380
|
+
"""
|
|
381
|
+
if not self._id:
|
|
382
|
+
raise ValueError(_TOOL_NOT_DEPLOYED_MSG)
|
|
383
|
+
if not self._client:
|
|
384
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
385
|
+
|
|
386
|
+
self._client.tools.delete(tool_id=self._id)
|
|
387
|
+
self._id = None
|
|
388
|
+
self._client = None
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def from_response(
|
|
392
|
+
cls,
|
|
393
|
+
response: ToolResponse,
|
|
394
|
+
client: Any = None,
|
|
395
|
+
) -> Tool:
|
|
396
|
+
"""Create a Tool instance from an API response.
|
|
397
|
+
|
|
398
|
+
This allows you to work with tools retrieved from the API
|
|
399
|
+
as full Tool instances with all methods available.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
response: The ToolResponse from an API call.
|
|
403
|
+
client: The Glaip client instance for API operations.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
A Tool instance initialized from the response.
|
|
407
|
+
|
|
408
|
+
Example:
|
|
409
|
+
>>> response = client.tools.get("tool-123")
|
|
410
|
+
>>> tool = Tool.from_response(response, client)
|
|
411
|
+
>>> script = tool.get_script()
|
|
412
|
+
"""
|
|
413
|
+
# Use tool_type from backend; infer CUSTOM when code is present but tool_type is missing
|
|
414
|
+
raw_type = getattr(response, "tool_type", None)
|
|
415
|
+
if raw_type is None and (
|
|
416
|
+
getattr(response, "tool_script", None) is not None or getattr(response, "tool_file", None) is not None
|
|
417
|
+
):
|
|
418
|
+
raw_type = ToolType.CUSTOM
|
|
419
|
+
|
|
420
|
+
tool = cls(
|
|
421
|
+
name=response.name,
|
|
422
|
+
id=response.id,
|
|
423
|
+
tool_type=raw_type,
|
|
424
|
+
description=getattr(response, "description", None),
|
|
425
|
+
tool_script=getattr(response, "tool_script", None),
|
|
426
|
+
tool_file=getattr(response, "tool_file", None),
|
|
427
|
+
framework=getattr(response, "framework", None),
|
|
428
|
+
version=getattr(response, "version", None),
|
|
429
|
+
tags=getattr(response, "tags", None),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if client:
|
|
433
|
+
tool._set_client(client)
|
|
434
|
+
|
|
435
|
+
return tool
|