alita-sdk 0.3.376__py3-none-any.whl → 0.3.423__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/configurations/bitbucket.py +95 -0
- alita_sdk/configurations/confluence.py +96 -1
- alita_sdk/configurations/gitlab.py +79 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/client.py +3 -2
- alita_sdk/runtime/clients/sandbox_client.py +8 -0
- alita_sdk/runtime/langchain/assistant.py +41 -38
- alita_sdk/runtime/langchain/constants.py +4 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
- alita_sdk/runtime/langchain/document_loaders/constants.py +28 -12
- alita_sdk/runtime/langchain/langraph_agent.py +88 -27
- alita_sdk/runtime/langchain/utils.py +24 -4
- alita_sdk/runtime/toolkits/application.py +8 -1
- alita_sdk/runtime/toolkits/tools.py +80 -49
- alita_sdk/runtime/tools/__init__.py +7 -2
- alita_sdk/runtime/tools/application.py +7 -0
- alita_sdk/runtime/tools/function.py +20 -28
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +104 -8
- alita_sdk/runtime/tools/llm.py +146 -114
- alita_sdk/runtime/tools/sandbox.py +166 -63
- alita_sdk/runtime/tools/vectorstore.py +3 -2
- alita_sdk/runtime/tools/vectorstore_base.py +4 -3
- alita_sdk/runtime/utils/utils.py +1 -0
- alita_sdk/tools/__init__.py +43 -31
- alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
- alita_sdk/tools/base_indexer_toolkit.py +75 -66
- alita_sdk/tools/code_indexer_toolkit.py +13 -3
- alita_sdk/tools/confluence/api_wrapper.py +29 -7
- alita_sdk/tools/confluence/loader.py +10 -0
- alita_sdk/tools/elitea_base.py +7 -7
- alita_sdk/tools/gitlab/api_wrapper.py +8 -9
- alita_sdk/tools/jira/api_wrapper.py +1 -1
- alita_sdk/tools/openapi/__init__.py +10 -1
- alita_sdk/tools/qtest/api_wrapper.py +298 -51
- alita_sdk/tools/sharepoint/api_wrapper.py +104 -33
- alita_sdk/tools/sharepoint/authorization_helper.py +175 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/utils/content_parser.py +27 -16
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +19 -6
- {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.423.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.423.dist-info}/RECORD +50 -50
- {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.423.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.423.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.376.dist-info → alita_sdk-0.3.423.dist-info}/top_level.txt +0 -0
|
@@ -2,21 +2,60 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
import subprocess
|
|
4
4
|
import os
|
|
5
|
-
from typing import Any, Type, Optional, Dict
|
|
6
|
-
from
|
|
7
|
-
from
|
|
5
|
+
from typing import Any, Type, Optional, Dict, List, Literal, Union
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from langchain_core.tools import BaseTool, BaseToolkit
|
|
10
|
+
from langchain_core.messages import ToolCall
|
|
11
|
+
from pydantic import BaseModel, create_model, ConfigDict, Field
|
|
8
12
|
from pydantic.fields import FieldInfo
|
|
9
13
|
|
|
10
14
|
logger = logging.getLogger(__name__)
|
|
11
15
|
|
|
16
|
+
name = "pyodide"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_tools(tools_list: list, alita_client=None, llm=None, memory_store=None):
|
|
20
|
+
"""
|
|
21
|
+
Get sandbox tools for the provided tool configurations.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
tools_list: List of tool configurations
|
|
25
|
+
alita_client: Alita client instance for sandbox tools
|
|
26
|
+
llm: LLM client instance (unused for sandbox)
|
|
27
|
+
memory_store: Optional memory store instance (unused for sandbox)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of sandbox tools
|
|
31
|
+
"""
|
|
32
|
+
all_tools = []
|
|
33
|
+
|
|
34
|
+
for tool in tools_list:
|
|
35
|
+
if tool.get('type') == 'sandbox' or tool.get('toolkit_name') == 'sandbox':
|
|
36
|
+
try:
|
|
37
|
+
toolkit_instance = SandboxToolkit.get_toolkit(
|
|
38
|
+
stateful=tool['settings'].get('stateful', False),
|
|
39
|
+
allow_net=tool['settings'].get('allow_net', True),
|
|
40
|
+
alita_client=alita_client,
|
|
41
|
+
toolkit_name=tool.get('toolkit_name', '')
|
|
42
|
+
)
|
|
43
|
+
all_tools.extend(toolkit_instance.get_tools())
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f"Error in sandbox toolkit get_tools: {e}")
|
|
46
|
+
logger.error(f"Tool config: {tool}")
|
|
47
|
+
raise
|
|
48
|
+
|
|
49
|
+
return all_tools
|
|
50
|
+
|
|
12
51
|
|
|
13
52
|
def _is_deno_available() -> bool:
|
|
14
53
|
"""Check if Deno is available in the PATH"""
|
|
15
54
|
try:
|
|
16
55
|
result = subprocess.run(
|
|
17
|
-
["deno", "--version"],
|
|
18
|
-
capture_output=True,
|
|
19
|
-
text=True,
|
|
56
|
+
["deno", "--version"],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
20
59
|
timeout=10
|
|
21
60
|
)
|
|
22
61
|
return result.returncode == 0
|
|
@@ -25,43 +64,17 @@ def _is_deno_available() -> bool:
|
|
|
25
64
|
|
|
26
65
|
|
|
27
66
|
def _setup_pyodide_cache_env() -> None:
|
|
28
|
-
"""Setup Pyodide caching environment variables for performance optimization"""
|
|
67
|
+
"""Setup Pyodide caching environment variables for performance optimization [NO-OP]"""
|
|
29
68
|
try:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if os.path.exists(cache_env_file):
|
|
33
|
-
with open(cache_env_file, 'r') as f:
|
|
34
|
-
for line in f:
|
|
35
|
-
line = line.strip()
|
|
36
|
-
if line.startswith('export ') and '=' in line:
|
|
37
|
-
# Parse export VAR=value format
|
|
38
|
-
var_assignment = line[7:] # Remove 'export '
|
|
39
|
-
if '=' in var_assignment:
|
|
40
|
-
key, value = var_assignment.split('=', 1)
|
|
41
|
-
# Remove quotes if present
|
|
42
|
-
value = value.strip('"').strip("'")
|
|
43
|
-
os.environ[key] = value
|
|
44
|
-
logger.debug(f"Set Pyodide cache env: {key}={value}")
|
|
45
|
-
|
|
46
|
-
# Set default caching environment variables if not already set
|
|
47
|
-
cache_defaults = {
|
|
48
|
-
'PYODIDE_PACKAGES_PATH': os.path.expanduser('~/.cache/pyodide'),
|
|
49
|
-
'DENO_DIR': os.path.expanduser('~/.cache/deno'),
|
|
50
|
-
'PYODIDE_CACHE_DIR': os.path.expanduser('~/.cache/pyodide'),
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for key, default_value in cache_defaults.items():
|
|
54
|
-
if key not in os.environ:
|
|
55
|
-
os.environ[key] = default_value
|
|
56
|
-
logger.debug(f"Set default Pyodide env: {key}={default_value}")
|
|
57
|
-
|
|
69
|
+
for key in ["SANDBOX_BASE", "DENO_DIR"]:
|
|
70
|
+
logger.info("Sandbox env: %s -> %s", key, os.environ.get(key, "n/a"))
|
|
58
71
|
except Exception as e:
|
|
59
72
|
logger.warning(f"Could not setup Pyodide cache environment: {e}")
|
|
60
73
|
|
|
61
74
|
|
|
62
75
|
# Create input schema for the sandbox tool
|
|
63
76
|
sandbox_tool_input = create_model(
|
|
64
|
-
"SandboxToolInput",
|
|
77
|
+
"SandboxToolInput",
|
|
65
78
|
code=(str, FieldInfo(description="Python code to execute in the sandbox environment"))
|
|
66
79
|
)
|
|
67
80
|
|
|
@@ -72,7 +85,7 @@ class PyodideSandboxTool(BaseTool):
|
|
|
72
85
|
This tool leverages langchain-sandbox to provide a safe environment for running untrusted Python code.
|
|
73
86
|
Optimized for performance with caching and stateless execution by default.
|
|
74
87
|
"""
|
|
75
|
-
|
|
88
|
+
|
|
76
89
|
name: str = "pyodide_sandbox"
|
|
77
90
|
description: str = """Execute Python code in a secure sandbox environment using Pyodide.
|
|
78
91
|
This tool allows safe execution of Python code without access to the host system.
|
|
@@ -81,7 +94,7 @@ class PyodideSandboxTool(BaseTool):
|
|
|
81
94
|
- Perform calculations or data analysis
|
|
82
95
|
- Test Python algorithms
|
|
83
96
|
- Run code that requires isolation from the host system
|
|
84
|
-
|
|
97
|
+
|
|
85
98
|
The sandbox supports most Python standard library modules and can install additional packages.
|
|
86
99
|
Note: File access and some system operations are restricted for security.
|
|
87
100
|
Optimized for performance with local caching (stateless by default for faster execution).
|
|
@@ -91,14 +104,37 @@ class PyodideSandboxTool(BaseTool):
|
|
|
91
104
|
allow_net: bool = True
|
|
92
105
|
session_bytes: Optional[bytes] = None
|
|
93
106
|
session_metadata: Optional[Dict] = None
|
|
94
|
-
|
|
107
|
+
alita_client: Optional[Any] = None
|
|
108
|
+
|
|
95
109
|
def __init__(self, **kwargs: Any) -> None:
|
|
96
110
|
super().__init__(**kwargs)
|
|
97
111
|
self._sandbox = None
|
|
98
112
|
# Setup caching environment for optimal performance
|
|
99
113
|
_setup_pyodide_cache_env()
|
|
100
114
|
self._initialize_sandbox()
|
|
101
|
-
|
|
115
|
+
|
|
116
|
+
def _prepare_pyodide_input(self, code: str) -> str:
|
|
117
|
+
"""Prepare input for PyodideSandboxTool by injecting state and alita_client into the code block."""
|
|
118
|
+
pyodide_predata = ""
|
|
119
|
+
|
|
120
|
+
# Add alita_client if available
|
|
121
|
+
if self.alita_client:
|
|
122
|
+
try:
|
|
123
|
+
# Get the directory of the current file and construct the path to sandbox_client.py
|
|
124
|
+
current_dir = Path(__file__).parent
|
|
125
|
+
sandbox_client_path = current_dir.parent / 'clients' / 'sandbox_client.py'
|
|
126
|
+
|
|
127
|
+
with open(sandbox_client_path, 'r') as f:
|
|
128
|
+
sandbox_client_code = f.read()
|
|
129
|
+
pyodide_predata += f"{sandbox_client_code}\n"
|
|
130
|
+
pyodide_predata += (f"alita_client = SandboxClient(base_url='{self.alita_client.base_url}',"
|
|
131
|
+
f"project_id={self.alita_client.project_id},"
|
|
132
|
+
f"auth_token='{self.alita_client.auth_token}')\n")
|
|
133
|
+
except FileNotFoundError:
|
|
134
|
+
logger.error(f"sandbox_client.py not found. Ensure the file exists.")
|
|
135
|
+
|
|
136
|
+
return f"#elitea simplified client\n{pyodide_predata}{code}"
|
|
137
|
+
|
|
102
138
|
def _initialize_sandbox(self) -> None:
|
|
103
139
|
"""Initialize the PyodideSandbox instance with optimized settings"""
|
|
104
140
|
try:
|
|
@@ -110,12 +146,22 @@ class PyodideSandboxTool(BaseTool):
|
|
|
110
146
|
)
|
|
111
147
|
logger.error(error_msg)
|
|
112
148
|
raise RuntimeError(error_msg)
|
|
113
|
-
|
|
149
|
+
|
|
114
150
|
from langchain_sandbox import PyodideSandbox
|
|
115
|
-
|
|
151
|
+
|
|
152
|
+
# Air-gapped settings
|
|
153
|
+
sandbox_base = os.environ.get("SANDBOX_BASE", os.path.expanduser('~/.cache/pyodide'))
|
|
154
|
+
sandbox_tmp = os.path.join(sandbox_base, "tmp")
|
|
155
|
+
deno_cache = os.environ.get("DENO_DIR", os.path.expanduser('~/.cache/deno'))
|
|
156
|
+
|
|
116
157
|
# Configure sandbox with performance optimizations
|
|
117
158
|
self._sandbox = PyodideSandbox(
|
|
118
159
|
stateful=self.stateful,
|
|
160
|
+
#
|
|
161
|
+
allow_env=["SANDBOX_BASE"],
|
|
162
|
+
allow_read=[sandbox_base, sandbox_tmp, deno_cache],
|
|
163
|
+
allow_write=[sandbox_tmp, deno_cache],
|
|
164
|
+
#
|
|
119
165
|
allow_net=self.allow_net,
|
|
120
166
|
# Use auto node_modules_dir for better caching
|
|
121
167
|
node_modules_dir="auto"
|
|
@@ -135,7 +181,7 @@ class PyodideSandboxTool(BaseTool):
|
|
|
135
181
|
except Exception as e:
|
|
136
182
|
logger.error(f"Failed to initialize PyodideSandbox: {e}")
|
|
137
183
|
raise
|
|
138
|
-
|
|
184
|
+
|
|
139
185
|
def _run(self, code: str) -> str:
|
|
140
186
|
"""
|
|
141
187
|
Synchronous version - runs the async method in a new event loop
|
|
@@ -144,7 +190,10 @@ class PyodideSandboxTool(BaseTool):
|
|
|
144
190
|
# Check if sandbox is initialized, if not try to initialize
|
|
145
191
|
if self._sandbox is None:
|
|
146
192
|
self._initialize_sandbox()
|
|
147
|
-
|
|
193
|
+
|
|
194
|
+
# Prepare code with state and client injection
|
|
195
|
+
prepared_code = self._prepare_pyodide_input(code)
|
|
196
|
+
|
|
148
197
|
# Check if we're already in an async context
|
|
149
198
|
try:
|
|
150
199
|
loop = asyncio.get_running_loop()
|
|
@@ -152,11 +201,11 @@ class PyodideSandboxTool(BaseTool):
|
|
|
152
201
|
# We'll need to use a different approach
|
|
153
202
|
import concurrent.futures
|
|
154
203
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
155
|
-
future = executor.submit(asyncio.run, self._arun(
|
|
204
|
+
future = executor.submit(asyncio.run, self._arun(prepared_code))
|
|
156
205
|
return future.result()
|
|
157
206
|
except RuntimeError:
|
|
158
207
|
# No running loop, safe to use asyncio.run
|
|
159
|
-
return asyncio.run(self._arun(
|
|
208
|
+
return asyncio.run(self._arun(prepared_code))
|
|
160
209
|
except (ImportError, RuntimeError) as e:
|
|
161
210
|
# Handle specific dependency errors gracefully
|
|
162
211
|
error_msg = str(e)
|
|
@@ -169,7 +218,7 @@ class PyodideSandboxTool(BaseTool):
|
|
|
169
218
|
except Exception as e:
|
|
170
219
|
logger.error(f"Error executing code in sandbox: {e}")
|
|
171
220
|
return f"Error executing code: {str(e)}"
|
|
172
|
-
|
|
221
|
+
|
|
173
222
|
async def _arun(self, code: str) -> str:
|
|
174
223
|
"""
|
|
175
224
|
Execute Python code in the Pyodide sandbox
|
|
@@ -177,19 +226,19 @@ class PyodideSandboxTool(BaseTool):
|
|
|
177
226
|
try:
|
|
178
227
|
if self._sandbox is None:
|
|
179
228
|
self._initialize_sandbox()
|
|
180
|
-
|
|
229
|
+
|
|
181
230
|
# Execute the code with session state if available
|
|
182
231
|
result = await self._sandbox.execute(
|
|
183
232
|
code,
|
|
184
233
|
session_bytes=self.session_bytes,
|
|
185
234
|
session_metadata=self.session_metadata
|
|
186
235
|
)
|
|
187
|
-
|
|
236
|
+
|
|
188
237
|
# Update session state for stateful execution
|
|
189
238
|
if self.stateful:
|
|
190
239
|
self.session_bytes = result.session_bytes
|
|
191
240
|
self.session_metadata = result.session_metadata
|
|
192
|
-
|
|
241
|
+
|
|
193
242
|
result_dict = {}
|
|
194
243
|
|
|
195
244
|
if result.result is not None:
|
|
@@ -212,10 +261,10 @@ class PyodideSandboxTool(BaseTool):
|
|
|
212
261
|
|
|
213
262
|
result_dict["execution_info"] = execution_info
|
|
214
263
|
return result_dict
|
|
215
|
-
|
|
264
|
+
|
|
216
265
|
except Exception as e:
|
|
217
266
|
logger.error(f"Error executing code in sandbox: {e}")
|
|
218
|
-
return f"Error executing code: {str(e)}"
|
|
267
|
+
return {"error": f"Error executing code: {str(e)}"}
|
|
219
268
|
|
|
220
269
|
|
|
221
270
|
class StatefulPyodideSandboxTool(PyodideSandboxTool):
|
|
@@ -223,7 +272,7 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
|
|
|
223
272
|
A stateful version of the PyodideSandboxTool that maintains state between executions.
|
|
224
273
|
This version preserves variables, imports, and function definitions across multiple tool calls.
|
|
225
274
|
"""
|
|
226
|
-
|
|
275
|
+
|
|
227
276
|
name: str = "stateful_pyodide_sandbox"
|
|
228
277
|
description: str = """Execute Python code in a stateful sandbox environment using Pyodide.
|
|
229
278
|
This tool maintains state between executions, preserving variables, imports, and function definitions.
|
|
@@ -232,41 +281,95 @@ class StatefulPyodideSandboxTool(PyodideSandboxTool):
|
|
|
232
281
|
- Maintain variables across multiple calls
|
|
233
282
|
- Develop complex programs step by step
|
|
234
283
|
- Preserve imported libraries and defined functions
|
|
235
|
-
|
|
284
|
+
|
|
236
285
|
The sandbox supports most Python standard library modules and can install additional packages.
|
|
237
286
|
Note: File access and some system operations are restricted for security.
|
|
238
287
|
"""
|
|
239
|
-
|
|
288
|
+
|
|
240
289
|
def __init__(self, **kwargs: Any) -> None:
|
|
241
290
|
kwargs['stateful'] = True # Force stateful mode
|
|
242
291
|
super().__init__(**kwargs)
|
|
243
292
|
|
|
244
293
|
|
|
245
294
|
# Factory function for creating sandbox tools
|
|
246
|
-
def create_sandbox_tool(stateful: bool = False, allow_net: bool = True) -> BaseTool:
|
|
295
|
+
def create_sandbox_tool(stateful: bool = False, allow_net: bool = True, alita_client: Optional[Any] = None) -> BaseTool:
|
|
247
296
|
"""
|
|
248
297
|
Factory function to create sandbox tools with specified configuration.
|
|
249
|
-
|
|
298
|
+
|
|
250
299
|
Note: This tool requires Deno to be installed and available in PATH.
|
|
251
300
|
For installation and optimization, run the bootstrap.sh script.
|
|
252
|
-
|
|
301
|
+
|
|
253
302
|
Args:
|
|
254
303
|
stateful: Whether to maintain state between executions (default: False for better performance)
|
|
255
304
|
allow_net: Whether to allow network access (for package installation)
|
|
256
|
-
|
|
305
|
+
|
|
257
306
|
Returns:
|
|
258
307
|
Configured sandbox tool instance
|
|
259
|
-
|
|
308
|
+
|
|
260
309
|
Raises:
|
|
261
310
|
ImportError: If langchain-sandbox is not installed
|
|
262
311
|
RuntimeError: If Deno is not found in PATH
|
|
263
|
-
|
|
312
|
+
|
|
264
313
|
Performance Notes:
|
|
265
314
|
- Stateless mode (default) is faster and avoids session state overhead
|
|
266
315
|
- Run bootstrap.sh script to enable local caching and reduce initialization time
|
|
267
316
|
- Cached wheels reduce package download time from ~4.76s to near-instant
|
|
268
317
|
"""
|
|
269
318
|
if stateful:
|
|
270
|
-
return StatefulPyodideSandboxTool(allow_net=allow_net)
|
|
319
|
+
return StatefulPyodideSandboxTool(allow_net=allow_net, alita_client=alita_client)
|
|
271
320
|
else:
|
|
272
|
-
return PyodideSandboxTool(stateful=False, allow_net=allow_net)
|
|
321
|
+
return PyodideSandboxTool(stateful=False, allow_net=allow_net, alita_client=alita_client)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class SandboxToolkit(BaseToolkit):
|
|
325
|
+
tools: List[BaseTool] = []
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def toolkit_config_schema() -> Type[BaseModel]:
|
|
329
|
+
# Create sample tools to get their schemas
|
|
330
|
+
sample_tools = [
|
|
331
|
+
PyodideSandboxTool(),
|
|
332
|
+
StatefulPyodideSandboxTool()
|
|
333
|
+
]
|
|
334
|
+
selected_tools = {x.name: x.args_schema.model_json_schema() for x in sample_tools}
|
|
335
|
+
|
|
336
|
+
return create_model(
|
|
337
|
+
'sandbox',
|
|
338
|
+
stateful=(bool, Field(default=False, description="Whether to maintain state between executions")),
|
|
339
|
+
allow_net=(bool, Field(default=True, description="Whether to allow network access for package installation")),
|
|
340
|
+
selected_tools=(List[Literal[tuple(selected_tools)]],
|
|
341
|
+
Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
|
|
342
|
+
|
|
343
|
+
__config__=ConfigDict(json_schema_extra={
|
|
344
|
+
'metadata': {
|
|
345
|
+
"label": "Python Sandbox",
|
|
346
|
+
"icon_url": "sandbox.svg",
|
|
347
|
+
"hidden": False,
|
|
348
|
+
"categories": ["code", "execution", "internal_tool"],
|
|
349
|
+
"extra_categories": ["python", "pyodide", "sandbox", "code execution"],
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
@classmethod
|
|
355
|
+
def get_toolkit(cls, stateful: bool = False, allow_net: bool = True, alita_client=None, **kwargs):
|
|
356
|
+
"""
|
|
357
|
+
Get toolkit with sandbox tools.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
stateful: Whether to maintain state between executions
|
|
361
|
+
allow_net: Whether to allow network access
|
|
362
|
+
alita_client: Alita client instance for sandbox tools
|
|
363
|
+
**kwargs: Additional arguments
|
|
364
|
+
"""
|
|
365
|
+
tools = []
|
|
366
|
+
|
|
367
|
+
if stateful:
|
|
368
|
+
tools.append(StatefulPyodideSandboxTool(allow_net=allow_net, alita_client=alita_client))
|
|
369
|
+
else:
|
|
370
|
+
tools.append(PyodideSandboxTool(stateful=False, allow_net=allow_net, alita_client=alita_client))
|
|
371
|
+
|
|
372
|
+
return cls(tools=tools)
|
|
373
|
+
|
|
374
|
+
def get_tools(self):
|
|
375
|
+
return self.tools
|
|
@@ -215,7 +215,7 @@ class VectorStoreWrapper(BaseToolApiWrapper):
|
|
|
215
215
|
"""List all collections in the vectorstore.
|
|
216
216
|
Returns a list of collection names, or if no collections exist,
|
|
217
217
|
returns a dict with an empty list and a message."""
|
|
218
|
-
raw = self.vector_adapter.
|
|
218
|
+
raw = self.vector_adapter.list_collections(self)
|
|
219
219
|
# Normalize raw result to a list of names
|
|
220
220
|
if not raw:
|
|
221
221
|
# No collections found
|
|
@@ -414,7 +414,8 @@ class VectorStoreWrapper(BaseToolApiWrapper):
|
|
|
414
414
|
return {"status": "error", "message": f"Error: {format_exc()}"}
|
|
415
415
|
if _documents:
|
|
416
416
|
add_documents(vectorstore=self.vectorstore, documents=_documents)
|
|
417
|
-
return {"status": "ok", "message": f"successfully indexed {documents_count} documents"
|
|
417
|
+
return {"status": "ok", "message": f"successfully indexed {documents_count} documents" if documents_count > 0
|
|
418
|
+
else "No new documents to index."}
|
|
418
419
|
|
|
419
420
|
def search_documents(self, query:str, doctype: str = 'code',
|
|
420
421
|
filter:dict|str={}, cut_off: float=0.5,
|
|
@@ -208,10 +208,10 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
|
|
|
208
208
|
logger.error(f"Error during similarity search: {str(e)}")
|
|
209
209
|
raise ToolException(f"Search failed: {str(e)}")
|
|
210
210
|
|
|
211
|
-
def
|
|
211
|
+
def list_collections(self) -> List[str]:
|
|
212
212
|
"""List all collections in the vectorstore."""
|
|
213
213
|
|
|
214
|
-
collections = self.vector_adapter.
|
|
214
|
+
collections = self.vector_adapter.list_collections(self)
|
|
215
215
|
if not collections:
|
|
216
216
|
return "No indexed collections"
|
|
217
217
|
return collections
|
|
@@ -308,7 +308,8 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
|
|
|
308
308
|
return {"status": "error", "message": f"Error: {format_exc()}"}
|
|
309
309
|
if _documents:
|
|
310
310
|
add_documents(vectorstore=self.vectorstore, documents=_documents)
|
|
311
|
-
return {"status": "ok", "message": f"successfully indexed {documents_count} documents"
|
|
311
|
+
return {"status": "ok", "message": f"successfully indexed {documents_count} documents" if documents_count > 0
|
|
312
|
+
else "no documents to index"}
|
|
312
313
|
|
|
313
314
|
def search_documents(self, query:str, doctype: str = 'code',
|
|
314
315
|
filter:dict|str={}, cut_off: float=0.5,
|
alita_sdk/runtime/utils/utils.py
CHANGED
|
@@ -14,6 +14,7 @@ class IndexerKeywords(Enum):
|
|
|
14
14
|
INDEX_META_TYPE = 'index_meta'
|
|
15
15
|
INDEX_META_IN_PROGRESS = 'in_progress'
|
|
16
16
|
INDEX_META_COMPLETED = 'completed'
|
|
17
|
+
INDEX_META_FAILED = 'failed'
|
|
17
18
|
|
|
18
19
|
# This pattern matches characters that are NOT alphanumeric, underscores, or hyphens
|
|
19
20
|
clean_string_pattern = re.compile(r'[^a-zA-Z0-9_.-]')
|
alita_sdk/tools/__init__.py
CHANGED
|
@@ -90,62 +90,74 @@ available_count = len(AVAILABLE_TOOLS)
|
|
|
90
90
|
total_attempted = len(AVAILABLE_TOOLS) + len(FAILED_IMPORTS)
|
|
91
91
|
logger.info(f"Tool imports completed: {available_count}/{total_attempted} successful")
|
|
92
92
|
|
|
93
|
+
|
|
93
94
|
def get_tools(tools_list, alita, llm, store: Optional[BaseStore] = None, *args, **kwargs):
|
|
94
95
|
tools = []
|
|
96
|
+
|
|
95
97
|
for tool in tools_list:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
tool
|
|
98
|
+
settings = tool.get('settings')
|
|
99
|
+
|
|
100
|
+
# Skip tools without settings early
|
|
101
|
+
if not settings:
|
|
102
|
+
logger.warning(f"Tool '{tool.get('type', '')}' has no settings, skipping...")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Validate tool names once
|
|
106
|
+
selected_tools = settings.get('selected_tools', [])
|
|
107
|
+
invalid_tools = [name for name in selected_tools if isinstance(name, str) and name.startswith('_')]
|
|
108
|
+
if invalid_tools:
|
|
109
|
+
raise ValueError(f"Tool names {invalid_tools} from toolkit '{tool.get('type', '')}' cannot start with '_'")
|
|
110
|
+
|
|
111
|
+
# Cache tool type and add common settings
|
|
104
112
|
tool_type = tool['type']
|
|
113
|
+
settings['alita'] = alita
|
|
114
|
+
settings['llm'] = llm
|
|
115
|
+
settings['store'] = store
|
|
116
|
+
|
|
117
|
+
# Set pgvector collection schema if present
|
|
118
|
+
if settings.get('pgvector_configuration'):
|
|
119
|
+
settings['pgvector_configuration']['collection_schema'] = str(tool['id'])
|
|
105
120
|
|
|
106
|
-
# Handle special cases
|
|
121
|
+
# Handle ADO special cases
|
|
107
122
|
if tool_type in ['ado_boards', 'ado_wiki', 'ado_plans']:
|
|
108
123
|
tools.extend(AVAILABLE_TOOLS['ado']['get_tools'](tool_type, tool))
|
|
124
|
+
continue
|
|
109
125
|
|
|
110
|
-
#
|
|
111
|
-
|
|
126
|
+
# Handle ADO repos aliases
|
|
127
|
+
if tool_type in ['ado_repos', 'azure_devops_repos'] and 'ado_repos' in AVAILABLE_TOOLS:
|
|
112
128
|
try:
|
|
113
|
-
|
|
114
|
-
tools.extend(get_tools_func(tool))
|
|
115
|
-
|
|
129
|
+
tools.extend(AVAILABLE_TOOLS['ado_repos']['get_tools'](tool))
|
|
116
130
|
except Exception as e:
|
|
117
|
-
logger.error(f"Error getting
|
|
118
|
-
|
|
131
|
+
logger.error(f"Error getting ADO repos tools: {e}")
|
|
132
|
+
continue
|
|
119
133
|
|
|
120
|
-
# Handle
|
|
121
|
-
|
|
134
|
+
# Handle standard tools
|
|
135
|
+
if tool_type in AVAILABLE_TOOLS and 'get_tools' in AVAILABLE_TOOLS[tool_type]:
|
|
122
136
|
try:
|
|
123
|
-
|
|
124
|
-
tools.extend(get_tools_func(tool))
|
|
137
|
+
tools.extend(AVAILABLE_TOOLS[tool_type]['get_tools'](tool))
|
|
125
138
|
except Exception as e:
|
|
126
|
-
logger.error(f"Error getting
|
|
139
|
+
logger.error(f"Error getting tools for {tool_type}: {e}")
|
|
140
|
+
raise ToolException(f"Error getting tools for {tool_type}: {e}")
|
|
141
|
+
continue
|
|
127
142
|
|
|
128
143
|
# Handle custom modules
|
|
129
|
-
|
|
144
|
+
if settings.get("module"):
|
|
130
145
|
try:
|
|
131
|
-
settings = tool.get("settings", {})
|
|
132
146
|
mod = import_module(settings.pop("module"))
|
|
133
147
|
tkitclass = getattr(mod, settings.pop("class"))
|
|
134
|
-
|
|
135
|
-
get_toolkit_params = tool["settings"].copy()
|
|
148
|
+
get_toolkit_params = settings.copy()
|
|
136
149
|
get_toolkit_params["name"] = tool.get("name")
|
|
137
|
-
#
|
|
138
150
|
toolkit = tkitclass.get_toolkit(**get_toolkit_params)
|
|
139
151
|
tools.extend(toolkit.get_tools())
|
|
140
152
|
except Exception as e:
|
|
141
153
|
logger.error(f"Error in getting custom toolkit: {e}")
|
|
154
|
+
continue
|
|
142
155
|
|
|
156
|
+
# Tool not available
|
|
157
|
+
if tool_type in FAILED_IMPORTS:
|
|
158
|
+
logger.warning(f"Tool '{tool_type}' is not available: {FAILED_IMPORTS[tool_type]}")
|
|
143
159
|
else:
|
|
144
|
-
|
|
145
|
-
if tool_type in FAILED_IMPORTS:
|
|
146
|
-
logger.warning(f"Tool '{tool_type}' is not available: {FAILED_IMPORTS[tool_type]}")
|
|
147
|
-
else:
|
|
148
|
-
logger.warning(f"Unknown tool type: {tool_type}")
|
|
160
|
+
logger.warning(f"Unknown tool type: {tool_type}")
|
|
149
161
|
|
|
150
162
|
return tools
|
|
151
163
|
|
|
@@ -329,11 +329,14 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
|
|
|
329
329
|
parsed_item.update(fields_data)
|
|
330
330
|
|
|
331
331
|
# extract relations if any
|
|
332
|
-
relations_data =
|
|
332
|
+
relations_data = None
|
|
333
|
+
if expand and str(expand).lower() in ("relations", "all"):
|
|
334
|
+
try:
|
|
335
|
+
relations_data = getattr(work_item, 'relations', None)
|
|
336
|
+
except KeyError:
|
|
337
|
+
relations_data = None
|
|
333
338
|
if relations_data:
|
|
334
|
-
parsed_item['relations'] = []
|
|
335
|
-
for relation in relations_data:
|
|
336
|
-
parsed_item['relations'].append(relation.as_dict())
|
|
339
|
+
parsed_item['relations'] = [relation.as_dict() for relation in relations_data]
|
|
337
340
|
|
|
338
341
|
if parse_attachments:
|
|
339
342
|
# describe images in work item fields if present
|
|
@@ -344,13 +347,19 @@ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
|
|
|
344
347
|
for img in images:
|
|
345
348
|
src = img.get('src')
|
|
346
349
|
if src:
|
|
347
|
-
description = self.parse_attachment_by_url(src, image_description_prompt)
|
|
350
|
+
description = self.parse_attachment_by_url(src, image_description_prompt=image_description_prompt)
|
|
348
351
|
img['image-description'] = description
|
|
349
352
|
parsed_item[field_name] = str(soup)
|
|
350
353
|
# parse attached documents if present
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
+
for relation in parsed_item.get('relations', []):
|
|
355
|
+
# Only process actual file attachments
|
|
356
|
+
if relation.get('rel') == 'AttachedFile':
|
|
357
|
+
file_name = relation.get('attributes', {}).get('name')
|
|
358
|
+
if file_name:
|
|
359
|
+
try:
|
|
360
|
+
relation['content'] = self.parse_attachment_by_url(relation['url'], file_name, image_description_prompt=image_description_prompt)
|
|
361
|
+
except Exception as att_e:
|
|
362
|
+
logger.warning(f"Failed to parse attachment {file_name}: {att_e}")
|
|
354
363
|
|
|
355
364
|
|
|
356
365
|
return parsed_item
|