glaip-sdk 0.6.25__py3-none-any.whl → 0.7.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/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/slash/tui/__init__.py +10 -1
- glaip_sdk/cli/slash/tui/context.py +51 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/client/agents.py +1 -1
- glaip_sdk/client/main.py +1 -1
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/registry/tool.py +193 -81
- glaip_sdk/tools/base.py +41 -10
- glaip_sdk/utils/import_resolver.py +40 -2
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/METADATA +2 -2
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/RECORD +51 -18
- glaip_sdk/cli/commands/agents.py +0 -1502
- glaip_sdk/cli/commands/mcps.py +0 -1355
- glaip_sdk/cli/commands/tools.py +0 -575
- /glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/top_level.txt +0 -0
glaip_sdk/client/tools.py
CHANGED
|
@@ -103,6 +103,9 @@ class ToolClient(BaseClient):
|
|
|
103
103
|
def _prepare_upload_data(self, name: str, framework: str, description: str | None = None, **kwargs) -> dict:
|
|
104
104
|
"""Prepare upload data dictionary.
|
|
105
105
|
|
|
106
|
+
Uses the same payload building logic as _build_create_payload to ensure
|
|
107
|
+
consistency between upload and metadata-only tool creation.
|
|
108
|
+
|
|
106
109
|
Args:
|
|
107
110
|
name: Tool name
|
|
108
111
|
framework: Tool framework
|
|
@@ -112,28 +115,19 @@ class ToolClient(BaseClient):
|
|
|
112
115
|
Returns:
|
|
113
116
|
dict: Upload data dictionary
|
|
114
117
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"framework": framework,
|
|
118
|
-
"type": kwargs.pop("tool_type", DEFAULT_TOOL_TYPE), # Default to custom
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if description:
|
|
122
|
-
data["description"] = description
|
|
123
|
-
|
|
124
|
-
# Handle tags if provided in kwargs
|
|
125
|
-
if kwargs.get("tags"):
|
|
126
|
-
if isinstance(kwargs["tags"], list):
|
|
127
|
-
data["tags"] = ",".join(kwargs["tags"])
|
|
128
|
-
else:
|
|
129
|
-
data["tags"] = kwargs["tags"]
|
|
118
|
+
# Extract tool_type from kwargs if present, defaulting to DEFAULT_TOOL_TYPE
|
|
119
|
+
tool_type = kwargs.pop("tool_type", DEFAULT_TOOL_TYPE)
|
|
130
120
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
# Use _build_create_payload to build the payload consistently
|
|
122
|
+
payload = self._build_create_payload(
|
|
123
|
+
name=name,
|
|
124
|
+
description=description,
|
|
125
|
+
framework=framework,
|
|
126
|
+
tool_type=tool_type,
|
|
127
|
+
**kwargs,
|
|
128
|
+
)
|
|
135
129
|
|
|
136
|
-
return
|
|
130
|
+
return payload
|
|
137
131
|
|
|
138
132
|
def _upload_tool_file(self, file_path: str, upload_data: dict) -> Tool:
|
|
139
133
|
"""Upload tool file to server.
|
|
@@ -439,9 +433,44 @@ class ToolClient(BaseClient):
|
|
|
439
433
|
except OSError:
|
|
440
434
|
pass # Ignore cleanup errors
|
|
441
435
|
|
|
442
|
-
def update_tool(self, tool_id: str, **kwargs) -> Tool:
|
|
443
|
-
"""Update an existing tool.
|
|
444
|
-
|
|
436
|
+
def update_tool(self, tool_id: str | Tool, **kwargs) -> Tool:
|
|
437
|
+
"""Update an existing tool.
|
|
438
|
+
|
|
439
|
+
Notes:
|
|
440
|
+
- Payload construction is centralized via ``_build_update_payload`` to keep metadata
|
|
441
|
+
update and upload update flows consistent.
|
|
442
|
+
- Accepts either a tool ID or a ``Tool`` instance (avoids an extra fetch when callers
|
|
443
|
+
already have the current tool).
|
|
444
|
+
"""
|
|
445
|
+
# Backward-compatible: allow passing a Tool instance to avoid an extra fetch.
|
|
446
|
+
if isinstance(tool_id, Tool):
|
|
447
|
+
current_tool = tool_id
|
|
448
|
+
if not current_tool.id:
|
|
449
|
+
raise ValueError("Tool instance has no id; cannot update.")
|
|
450
|
+
tool_id_value = str(current_tool.id)
|
|
451
|
+
else:
|
|
452
|
+
current_tool = None
|
|
453
|
+
tool_id_value = tool_id
|
|
454
|
+
|
|
455
|
+
if not kwargs:
|
|
456
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json={})
|
|
457
|
+
response = ToolResponse(**data)
|
|
458
|
+
return Tool.from_response(response, client=self)
|
|
459
|
+
|
|
460
|
+
if current_tool is None:
|
|
461
|
+
current_tool = self.get_tool_by_id(tool_id_value)
|
|
462
|
+
|
|
463
|
+
payload_kwargs = kwargs.copy()
|
|
464
|
+
name = payload_kwargs.pop("name", None)
|
|
465
|
+
description = payload_kwargs.pop("description", None)
|
|
466
|
+
update_payload = self._build_update_payload(
|
|
467
|
+
current_tool=current_tool,
|
|
468
|
+
name=name,
|
|
469
|
+
description=description,
|
|
470
|
+
**payload_kwargs,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
data = self._request("PUT", f"{TOOLS_ENDPOINT}{tool_id_value}", json=update_payload)
|
|
445
474
|
response = ToolResponse(**data)
|
|
446
475
|
return Tool.from_response(response, client=self)
|
|
447
476
|
|
glaip_sdk/registry/tool.py
CHANGED
|
@@ -54,6 +54,32 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
54
54
|
value = getattr(obj, attr, None)
|
|
55
55
|
return value if isinstance(value, str) else None
|
|
56
56
|
|
|
57
|
+
def _extract_name_from_instance(self, ref: Any) -> str | None:
|
|
58
|
+
"""Extract name from a non-type instance.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ref: The instance to extract name from.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The extracted name, or None if not found.
|
|
65
|
+
"""
|
|
66
|
+
if isinstance(ref, type):
|
|
67
|
+
return None
|
|
68
|
+
return self._get_string_attr(ref, "name")
|
|
69
|
+
|
|
70
|
+
def _extract_name_from_class(self, ref: Any) -> str | None:
|
|
71
|
+
"""Extract name from a class.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
ref: The class to extract name from.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The extracted name, or None if not found.
|
|
78
|
+
"""
|
|
79
|
+
if not isinstance(ref, type):
|
|
80
|
+
return None
|
|
81
|
+
return self._get_string_attr(ref, "name") or self._get_name_from_model_fields(ref)
|
|
82
|
+
|
|
57
83
|
def _extract_name(self, ref: Any) -> str:
|
|
58
84
|
"""Extract tool name from a reference.
|
|
59
85
|
|
|
@@ -81,31 +107,37 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
81
107
|
return ref.get("name") or ref.get("id") or ""
|
|
82
108
|
|
|
83
109
|
# Tool instance (not a class) with name attribute
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return name
|
|
110
|
+
name = self._extract_name_from_instance(ref)
|
|
111
|
+
if name:
|
|
112
|
+
return name
|
|
88
113
|
|
|
89
114
|
# Tool class - try direct attribute first, then model_fields
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return name
|
|
115
|
+
name = self._extract_name_from_class(ref)
|
|
116
|
+
if name:
|
|
117
|
+
return name
|
|
94
118
|
|
|
95
119
|
raise ValueError(f"Cannot extract name from: {ref}")
|
|
96
120
|
|
|
97
|
-
def
|
|
98
|
-
"""
|
|
121
|
+
def _cache_tool(self, tool: Tool, name: str) -> None:
|
|
122
|
+
"""Cache a tool by name and ID if available.
|
|
99
123
|
|
|
100
124
|
Args:
|
|
101
|
-
|
|
125
|
+
tool: The tool to cache.
|
|
126
|
+
name: The tool name.
|
|
127
|
+
"""
|
|
128
|
+
self._cache[name] = tool
|
|
129
|
+
if hasattr(tool, "id") and tool.id:
|
|
130
|
+
self._cache[tool.id] = tool
|
|
131
|
+
|
|
132
|
+
def _resolve_tool_instance(self, ref: Any, name: str) -> Tool | None:
|
|
133
|
+
"""Resolve a ToolClass instance.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
ref: The ToolClass instance to resolve.
|
|
102
137
|
name: The extracted tool name.
|
|
103
138
|
|
|
104
139
|
Returns:
|
|
105
|
-
The resolved
|
|
106
|
-
|
|
107
|
-
Raises:
|
|
108
|
-
ValueError: If the tool cannot be resolved.
|
|
140
|
+
The resolved tool, or None if not a ToolClass instance.
|
|
109
141
|
"""
|
|
110
142
|
# Lazy imports to avoid circular dependency
|
|
111
143
|
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
@@ -113,56 +145,23 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
113
145
|
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
114
146
|
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
115
147
|
|
|
116
|
-
# Tool instance from Tool.from_native() or Tool.from_langchain()
|
|
117
148
|
# Use try/except to handle mocked Tool class in tests
|
|
118
149
|
try:
|
|
119
150
|
is_tool_instance = isinstance(ref, ToolClass)
|
|
120
151
|
except TypeError:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if is_tool_instance:
|
|
124
|
-
# If Tool has an ID, it's already deployed - return as-is
|
|
125
|
-
if ref.id is not None:
|
|
126
|
-
logger.debug("Caching already deployed tool: %s", name)
|
|
127
|
-
self._cache[name] = ref
|
|
128
|
-
# Also cache by id for consistency with other resolution branches
|
|
129
|
-
self._cache[ref.id] = ref
|
|
130
|
-
return ref
|
|
131
|
-
|
|
132
|
-
# Tool.from_native() - look up on platform
|
|
133
|
-
if ref.tool_type == ToolType.NATIVE:
|
|
134
|
-
logger.info("Looking up native tool: %s", name)
|
|
135
|
-
tool = find_tool(name)
|
|
136
|
-
if tool:
|
|
137
|
-
self._cache[name] = tool
|
|
138
|
-
return tool
|
|
139
|
-
raise ValueError(f"Native tool not found on platform: {name}")
|
|
140
|
-
|
|
141
|
-
# Tool.from_langchain() - upload the tool_class
|
|
142
|
-
if ref.tool_class is not None:
|
|
143
|
-
logger.info("Uploading custom tool: %s", name)
|
|
144
|
-
tool = update_or_create_tool(ref.tool_class)
|
|
145
|
-
self._cache[name] = tool
|
|
146
|
-
if tool.id:
|
|
147
|
-
self._cache[tool.id] = tool
|
|
148
|
-
return tool
|
|
152
|
+
return None
|
|
149
153
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
f"Cannot resolve Tool instance: {ref}. "
|
|
153
|
-
f"Tool has no id, is not NATIVE type, and has no tool_class. "
|
|
154
|
-
f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
|
|
155
|
-
)
|
|
154
|
+
if not is_tool_instance:
|
|
155
|
+
return None
|
|
156
156
|
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
self._cache[name] = ref
|
|
163
|
-
return ref
|
|
157
|
+
# If Tool has an ID, it's already deployed - return as-is
|
|
158
|
+
if ref.id is not None:
|
|
159
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
160
|
+
self._cache_tool(ref, name)
|
|
161
|
+
return ref
|
|
164
162
|
|
|
165
|
-
|
|
163
|
+
# Tool.from_native() - look up on platform
|
|
164
|
+
if ref.tool_type == ToolType.NATIVE:
|
|
166
165
|
logger.info("Looking up native tool: %s", name)
|
|
167
166
|
tool = find_tool(name)
|
|
168
167
|
if tool:
|
|
@@ -170,32 +169,144 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
170
169
|
return tool
|
|
171
170
|
raise ValueError(f"Native tool not found on platform: {name}")
|
|
172
171
|
|
|
173
|
-
#
|
|
174
|
-
if
|
|
172
|
+
# Tool.from_langchain() - upload the tool_class
|
|
173
|
+
if ref.tool_class is not None:
|
|
175
174
|
logger.info("Uploading custom tool: %s", name)
|
|
176
|
-
tool = update_or_create_tool(ref)
|
|
177
|
-
self.
|
|
178
|
-
if tool.id:
|
|
179
|
-
self._cache[tool.id] = tool
|
|
175
|
+
tool = update_or_create_tool(ref.tool_class)
|
|
176
|
+
self._cache_tool(tool, name)
|
|
180
177
|
return tool
|
|
181
178
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return tool
|
|
189
|
-
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
179
|
+
# Unresolvable Tool instance - neither native nor has tool_class
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Cannot resolve Tool instance: {ref}. "
|
|
182
|
+
f"Tool has no id, is not NATIVE type, and has no tool_class. "
|
|
183
|
+
f"Ensure Tool is created via Tool.from_native() or Tool.from_langchain()."
|
|
184
|
+
)
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
186
|
+
def _resolve_deployed_tool(self, ref: Any, name: str) -> Tool | None:
|
|
187
|
+
"""Resolve an already deployed tool (has id/name attributes).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
ref: The tool reference to resolve.
|
|
191
|
+
name: The extracted tool name.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The resolved tool, or None if not a deployed tool.
|
|
195
|
+
"""
|
|
196
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
197
|
+
|
|
198
|
+
# Already deployed tool (not a ToolClass, but has id/name)
|
|
199
|
+
# This handles API response objects and backward compatibility
|
|
200
|
+
if not (hasattr(ref, "id") and hasattr(ref, "name") and not isinstance(ref, type)):
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
if ref.id is not None:
|
|
204
|
+
logger.debug("Caching already deployed tool: %s", name)
|
|
205
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
206
|
+
self._cache_tool(ref, name)
|
|
207
|
+
return ref
|
|
208
|
+
|
|
209
|
+
# Tool without ID (backward compatibility) - look up on platform
|
|
210
|
+
logger.info("Looking up native tool: %s", name)
|
|
211
|
+
tool = find_tool(name)
|
|
212
|
+
if tool:
|
|
213
|
+
# Use _cache_tool to cache by both name and ID if available
|
|
214
|
+
self._cache_tool(tool, name)
|
|
215
|
+
return tool
|
|
216
|
+
raise ValueError(f"Native tool not found on platform: {name}")
|
|
217
|
+
|
|
218
|
+
def _resolve_custom_tool(self, ref: Any, name: str) -> Tool | None:
|
|
219
|
+
"""Resolve a custom tool class.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
ref: The tool reference to resolve.
|
|
223
|
+
name: The extracted tool name.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The resolved tool, or None if not a custom tool.
|
|
227
|
+
"""
|
|
228
|
+
from glaip_sdk.utils.sync import update_or_create_tool # noqa: PLC0415
|
|
229
|
+
|
|
230
|
+
if not self._is_custom_tool(ref):
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
logger.info("Uploading custom tool: %s", name)
|
|
234
|
+
tool = update_or_create_tool(ref)
|
|
235
|
+
self._cache_tool(tool, name)
|
|
236
|
+
return tool
|
|
237
|
+
|
|
238
|
+
def _resolve_dict_tool(self, ref: Any, name: str) -> Tool | None:
|
|
239
|
+
"""Resolve a tool from a dict (API response).
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
ref: The dict to resolve.
|
|
243
|
+
name: The extracted tool name.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
The resolved tool, or None if not a dict.
|
|
247
|
+
"""
|
|
248
|
+
from glaip_sdk.tools.base import Tool as ToolClass # noqa: PLC0415
|
|
249
|
+
|
|
250
|
+
if not isinstance(ref, dict):
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
tool_id = ref.get("id")
|
|
254
|
+
if tool_id:
|
|
255
|
+
tool = ToolClass(id=tool_id, name=ref.get("name", ""))
|
|
256
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
257
|
+
self._cache_tool(tool, name)
|
|
258
|
+
return tool
|
|
259
|
+
raise ValueError(f"Tool dict missing 'id': {ref}")
|
|
260
|
+
|
|
261
|
+
def _resolve_string_tool(self, ref: Any, name: str) -> Tool | None:
|
|
262
|
+
"""Resolve a tool from a string name.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
ref: The string to resolve.
|
|
266
|
+
name: The extracted tool name.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The resolved tool, or None if not a string.
|
|
270
|
+
"""
|
|
271
|
+
from glaip_sdk.utils.discovery import find_tool # noqa: PLC0415
|
|
272
|
+
|
|
273
|
+
if not isinstance(ref, str):
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
logger.info("Looking up tool by name: %s", name)
|
|
277
|
+
tool = find_tool(name)
|
|
278
|
+
if tool:
|
|
279
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
280
|
+
self._cache_tool(tool, name)
|
|
281
|
+
return tool
|
|
282
|
+
raise ValueError(f"Tool not found on platform: {name}")
|
|
283
|
+
|
|
284
|
+
def _resolve_and_cache(self, ref: Any, name: str) -> Tool:
|
|
285
|
+
"""Resolve tool reference - upload if class, find if string/native.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
ref: The tool reference to resolve.
|
|
289
|
+
name: The extracted tool name.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
The resolved glaip_sdk.models.Tool object.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If the tool cannot be resolved.
|
|
296
|
+
"""
|
|
297
|
+
# Try each resolution strategy in order
|
|
298
|
+
resolvers = [
|
|
299
|
+
self._resolve_tool_instance,
|
|
300
|
+
self._resolve_deployed_tool,
|
|
301
|
+
self._resolve_custom_tool,
|
|
302
|
+
self._resolve_dict_tool,
|
|
303
|
+
self._resolve_string_tool,
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
for resolver in resolvers:
|
|
307
|
+
result = resolver(ref, name)
|
|
308
|
+
if result is not None:
|
|
309
|
+
return result
|
|
199
310
|
|
|
200
311
|
raise ValueError(f"Could not resolve tool reference: {ref}")
|
|
201
312
|
|
|
@@ -234,7 +345,8 @@ class ToolRegistry(BaseRegistry["Tool"]):
|
|
|
234
345
|
if ref.id is not None:
|
|
235
346
|
name = self._extract_name(ref)
|
|
236
347
|
if name not in self._cache:
|
|
237
|
-
|
|
348
|
+
# Use _cache_tool to cache by both name and ID for consistency
|
|
349
|
+
self._cache_tool(ref, name)
|
|
238
350
|
return ref
|
|
239
351
|
|
|
240
352
|
# Tool without ID (e.g., from Tool.from_native()) - needs platform lookup
|
glaip_sdk/tools/base.py
CHANGED
|
@@ -256,6 +256,9 @@ class Tool:
|
|
|
256
256
|
Returns:
|
|
257
257
|
A Tool reference that will be uploaded during Agent.deploy().
|
|
258
258
|
|
|
259
|
+
Raises:
|
|
260
|
+
ValueError: If the tool class has no valid string 'name' attribute or field.
|
|
261
|
+
|
|
259
262
|
Example:
|
|
260
263
|
>>> from langchain_core.tools import BaseTool
|
|
261
264
|
>>>
|
|
@@ -267,7 +270,42 @@ class Tool:
|
|
|
267
270
|
>>>
|
|
268
271
|
>>> greeting_tool = Tool.from_langchain(GreetingTool)
|
|
269
272
|
"""
|
|
270
|
-
|
|
273
|
+
# Extract name from tool_class to populate the name attribute
|
|
274
|
+
tool_name = cls._extract_tool_name(tool_class)
|
|
275
|
+
return cls(name=tool_name, tool_class=tool_class, type=ToolType.CUSTOM)
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def _extract_tool_name(tool_class: type) -> str:
|
|
279
|
+
"""Extract tool name from a LangChain tool class.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
tool_class: A LangChain BaseTool subclass.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
The extracted tool name.
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
ValueError: If name cannot be extracted or is not a valid string.
|
|
289
|
+
"""
|
|
290
|
+
# Try model_fields first (Pydantic v2)
|
|
291
|
+
if hasattr(tool_class, "model_fields"):
|
|
292
|
+
name_field = tool_class.model_fields.get("name")
|
|
293
|
+
if name_field and name_field.default:
|
|
294
|
+
# Validate that default is actually a string
|
|
295
|
+
if isinstance(name_field.default, str):
|
|
296
|
+
return name_field.default
|
|
297
|
+
|
|
298
|
+
# Try direct name attribute
|
|
299
|
+
if hasattr(tool_class, "name"):
|
|
300
|
+
name_attr = getattr(tool_class, "name")
|
|
301
|
+
if isinstance(name_attr, str):
|
|
302
|
+
return name_attr
|
|
303
|
+
|
|
304
|
+
# If we can't extract the name, raise an error
|
|
305
|
+
raise ValueError(
|
|
306
|
+
f"Cannot extract name from tool class {tool_class.__name__}. "
|
|
307
|
+
f"Ensure the tool class has a 'name' attribute or field with a valid string value."
|
|
308
|
+
)
|
|
271
309
|
|
|
272
310
|
def get_import_path(self) -> str | None:
|
|
273
311
|
"""Get the import path for custom tools.
|
|
@@ -292,15 +330,8 @@ class Tool:
|
|
|
292
330
|
return self.name
|
|
293
331
|
|
|
294
332
|
if self.tool_class is not None:
|
|
295
|
-
#
|
|
296
|
-
|
|
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
|
|
333
|
+
# Reuse extraction logic for consistency
|
|
334
|
+
return self._extract_tool_name(self.tool_class)
|
|
304
335
|
|
|
305
336
|
raise ValueError(f"Cannot determine name for tool: {self}")
|
|
306
337
|
|
|
@@ -215,11 +215,49 @@ class ImportResolver:
|
|
|
215
215
|
True if import should be skipped.
|
|
216
216
|
"""
|
|
217
217
|
if isinstance(node, ast.ImportFrom):
|
|
218
|
-
return
|
|
218
|
+
return self._should_skip_import_from(node)
|
|
219
219
|
if isinstance(node, ast.Import):
|
|
220
|
-
return
|
|
220
|
+
return self._should_skip_regular_import(node)
|
|
221
221
|
return False
|
|
222
222
|
|
|
223
|
+
def _should_skip_import_from(self, node: ast.ImportFrom) -> bool:
|
|
224
|
+
"""Check if ImportFrom node should be skipped.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
node: ImportFrom node to check.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
True if import should be skipped.
|
|
231
|
+
"""
|
|
232
|
+
if not node.module:
|
|
233
|
+
return False
|
|
234
|
+
return self._is_module_excluded(node.module)
|
|
235
|
+
|
|
236
|
+
def _should_skip_regular_import(self, node: ast.Import) -> bool:
|
|
237
|
+
"""Check if Import node should be skipped.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
node: Import node to check.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
True if any alias should be skipped.
|
|
244
|
+
"""
|
|
245
|
+
return any(self._is_module_excluded(alias.name) for alias in node.names)
|
|
246
|
+
|
|
247
|
+
def _is_module_excluded(self, module_name: str) -> bool:
|
|
248
|
+
"""Check if a module name should be excluded.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
module_name: Module name to check.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
True if module is excluded.
|
|
255
|
+
"""
|
|
256
|
+
# Exact match for glaip_sdk or match excluded submodules with boundary
|
|
257
|
+
if module_name == "glaip_sdk":
|
|
258
|
+
return True
|
|
259
|
+
return any(module_name == m or module_name.startswith(m + ".") for m in self.EXCLUDED_MODULES)
|
|
260
|
+
|
|
223
261
|
@staticmethod
|
|
224
262
|
def _build_import_strings(future_imports: list, regular_imports: list) -> list[str]:
|
|
225
263
|
"""Build formatted import strings from import nodes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glaip-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python SDK and CLI for GL AIP (GDP Labs AI Agent Package) - Build, run, and manage AI agents
|
|
5
5
|
Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
|
|
6
6
|
License: MIT
|
|
@@ -144,7 +144,7 @@ print("--- Stream complete ---")
|
|
|
144
144
|
|
|
145
145
|
🎉 **SDK Success!** You're now ready to build AI-powered applications with Python.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
______________________________________________________________________
|
|
148
148
|
|
|
149
149
|
## 💻 Hello World - CLI
|
|
150
150
|
|