alita-sdk 0.3.497__py3-none-any.whl → 0.3.515__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/cli/inventory.py +12 -195
- alita_sdk/community/inventory/__init__.py +12 -0
- alita_sdk/community/inventory/toolkit.py +9 -5
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/configurations/ado.py +144 -0
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +2 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +2 -2
- alita_sdk/runtime/clients/client.py +24 -19
- alita_sdk/runtime/clients/sandbox_client.py +14 -0
- alita_sdk/runtime/langchain/assistant.py +48 -2
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +2 -1
- alita_sdk/runtime/langchain/document_loaders/constants.py +2 -1
- alita_sdk/runtime/langchain/langraph_agent.py +8 -9
- alita_sdk/runtime/langchain/utils.py +6 -1
- alita_sdk/runtime/toolkits/artifact.py +14 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +26 -157
- alita_sdk/runtime/toolkits/planning.py +10 -5
- alita_sdk/runtime/toolkits/tools.py +23 -7
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/artifact.py +139 -6
- alita_sdk/runtime/tools/llm.py +20 -10
- alita_sdk/runtime/tools/mcp_remote_tool.py +2 -3
- alita_sdk/runtime/tools/mcp_server_tool.py +2 -4
- alita_sdk/runtime/utils/AlitaCallback.py +30 -1
- alita_sdk/runtime/utils/mcp_client.py +33 -6
- alita_sdk/runtime/utils/mcp_oauth.py +125 -8
- alita_sdk/runtime/utils/mcp_sse_client.py +35 -6
- alita_sdk/runtime/utils/utils.py +2 -0
- alita_sdk/tools/__init__.py +15 -0
- alita_sdk/tools/ado/repos/__init__.py +10 -12
- alita_sdk/tools/ado/test_plan/__init__.py +23 -8
- alita_sdk/tools/ado/wiki/__init__.py +24 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +21 -7
- alita_sdk/tools/ado/work_item/__init__.py +24 -8
- alita_sdk/tools/advanced_jira_mining/__init__.py +10 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +12 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +9 -7
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +25 -0
- alita_sdk/tools/bitbucket/__init__.py +14 -10
- alita_sdk/tools/bitbucket/api_wrapper.py +50 -2
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/cloud/aws/__init__.py +9 -7
- alita_sdk/tools/cloud/azure/__init__.py +9 -7
- alita_sdk/tools/cloud/gcp/__init__.py +9 -7
- alita_sdk/tools/cloud/k8s/__init__.py +9 -7
- alita_sdk/tools/code/linter/__init__.py +9 -8
- alita_sdk/tools/code/sonar/__init__.py +9 -7
- alita_sdk/tools/confluence/__init__.py +15 -10
- alita_sdk/tools/custom_open_api/__init__.py +11 -5
- alita_sdk/tools/elastic/__init__.py +10 -8
- alita_sdk/tools/elitea_base.py +387 -9
- alita_sdk/tools/figma/__init__.py +8 -7
- alita_sdk/tools/github/__init__.py +12 -14
- alita_sdk/tools/github/github_client.py +68 -2
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +14 -11
- alita_sdk/tools/gitlab/api_wrapper.py +81 -1
- alita_sdk/tools/gitlab_org/__init__.py +9 -8
- alita_sdk/tools/google/bigquery/__init__.py +12 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +9 -8
- alita_sdk/tools/jira/__init__.py +15 -10
- alita_sdk/tools/keycloak/__init__.py +10 -8
- alita_sdk/tools/localgit/__init__.py +8 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/ocr/__init__.py +10 -8
- alita_sdk/tools/openapi/__init__.py +6 -2
- alita_sdk/tools/pandas/__init__.py +9 -7
- alita_sdk/tools/postman/__init__.py +10 -11
- alita_sdk/tools/pptx/__init__.py +9 -9
- alita_sdk/tools/qtest/__init__.py +9 -8
- alita_sdk/tools/rally/__init__.py +9 -8
- alita_sdk/tools/report_portal/__init__.py +11 -9
- alita_sdk/tools/salesforce/__init__.py +9 -9
- alita_sdk/tools/servicenow/__init__.py +10 -8
- alita_sdk/tools/sharepoint/__init__.py +9 -8
- alita_sdk/tools/slack/__init__.py +8 -7
- alita_sdk/tools/sql/__init__.py +9 -8
- alita_sdk/tools/testio/__init__.py +9 -8
- alita_sdk/tools/testrail/__init__.py +10 -8
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/xray/__init__.py +10 -8
- alita_sdk/tools/yagmail/__init__.py +8 -3
- alita_sdk/tools/zephyr/__init__.py +8 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/__init__.py +9 -8
- alita_sdk/tools/zephyr_scale/__init__.py +9 -8
- alita_sdk/tools/zephyr_squad/__init__.py +9 -8
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/RECORD +108 -105
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import requests
|
|
1
2
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr
|
|
2
3
|
|
|
3
4
|
|
|
@@ -16,3 +17,85 @@ class TestIOConfiguration(BaseModel):
|
|
|
16
17
|
)
|
|
17
18
|
endpoint: str = Field(description="TestIO endpoint")
|
|
18
19
|
api_key: SecretStr = Field(description="TestIO API Key")
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def check_connection(settings: dict) -> str | None:
|
|
23
|
+
"""
|
|
24
|
+
Test the connection to TestIO API.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
settings: Dictionary containing 'endpoint' and 'api_key' (both required)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None if connection is successful, error message string otherwise
|
|
31
|
+
"""
|
|
32
|
+
endpoint = settings.get("endpoint")
|
|
33
|
+
if endpoint is None or endpoint == "":
|
|
34
|
+
if endpoint == "":
|
|
35
|
+
return "Endpoint cannot be empty"
|
|
36
|
+
return "Endpoint is required"
|
|
37
|
+
|
|
38
|
+
# Validate endpoint format
|
|
39
|
+
if not isinstance(endpoint, str):
|
|
40
|
+
return "Endpoint must be a string"
|
|
41
|
+
|
|
42
|
+
endpoint = endpoint.strip()
|
|
43
|
+
if not endpoint:
|
|
44
|
+
return "Endpoint cannot be empty"
|
|
45
|
+
if not endpoint.startswith(("http://", "https://")):
|
|
46
|
+
return "Endpoint must start with http:// or https://"
|
|
47
|
+
|
|
48
|
+
# Remove trailing slash for consistency
|
|
49
|
+
endpoint = endpoint.rstrip("/")
|
|
50
|
+
|
|
51
|
+
api_key = settings.get("api_key")
|
|
52
|
+
if api_key is None:
|
|
53
|
+
return "API key is required"
|
|
54
|
+
|
|
55
|
+
# Extract secret value if it's a SecretStr
|
|
56
|
+
if hasattr(api_key, "get_secret_value"):
|
|
57
|
+
api_key = api_key.get_secret_value()
|
|
58
|
+
|
|
59
|
+
# Validate API key is not empty
|
|
60
|
+
if not api_key or not api_key.strip():
|
|
61
|
+
return "API key cannot be empty"
|
|
62
|
+
|
|
63
|
+
# Verification strategy:
|
|
64
|
+
# Use an auth-required endpoint and a single, explicit auth scheme:
|
|
65
|
+
# Authorization: Token <token>
|
|
66
|
+
url = f"{endpoint}/customer/v2/products"
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
resp = TestIOConfiguration._get_with_token(url, api_key)
|
|
70
|
+
|
|
71
|
+
if resp.status_code == 200:
|
|
72
|
+
return None # Connection successful
|
|
73
|
+
if resp.status_code == 401:
|
|
74
|
+
return "Invalid token"
|
|
75
|
+
if resp.status_code == 403:
|
|
76
|
+
return "Access forbidden - token has no access to /customer/v2/products"
|
|
77
|
+
if resp.status_code == 404:
|
|
78
|
+
return "Invalid endpoint. Verify TestIO base endpoint."
|
|
79
|
+
if resp.status_code == 429:
|
|
80
|
+
return "Rate limited - please try again later"
|
|
81
|
+
if 500 <= resp.status_code <= 599:
|
|
82
|
+
return f"TestIO service error (HTTP {resp.status_code})"
|
|
83
|
+
return f"Connection failed (HTTP {resp.status_code})"
|
|
84
|
+
|
|
85
|
+
except requests.exceptions.Timeout:
|
|
86
|
+
return "Connection timeout - TestIO did not respond within 10 seconds"
|
|
87
|
+
except requests.exceptions.ConnectionError:
|
|
88
|
+
return "Connection error - unable to reach TestIO. Check endpoint URL and network."
|
|
89
|
+
except requests.exceptions.RequestException as e:
|
|
90
|
+
return f"Request failed: {str(e)}"
|
|
91
|
+
except Exception:
|
|
92
|
+
return "Unexpected error during TestIO connection check"
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _get_with_token(url: str, token: str) -> requests.Response:
|
|
96
|
+
"""Perform an authenticated GET using `Authorization: Token <token>`."""
|
|
97
|
+
return requests.get(
|
|
98
|
+
url,
|
|
99
|
+
headers={"Authorization": f"Token {token}"},
|
|
100
|
+
timeout=10,
|
|
101
|
+
)
|
|
@@ -38,8 +38,8 @@ class Artifact:
|
|
|
38
38
|
if len(data) == 0:
|
|
39
39
|
# empty file might be created
|
|
40
40
|
return ""
|
|
41
|
-
if isinstance(data, dict) and data
|
|
42
|
-
return f"{data['error']}. {data
|
|
41
|
+
if isinstance(data, dict) and data.get('error'):
|
|
42
|
+
return f"{data['error']}. {data.get('content', '')}"
|
|
43
43
|
detected = chardet.detect(data)
|
|
44
44
|
if detected['encoding'] is not None:
|
|
45
45
|
try:
|
|
@@ -20,9 +20,8 @@ from .prompt import AlitaPrompt
|
|
|
20
20
|
from .datasource import AlitaDataSource
|
|
21
21
|
from .artifact import Artifact
|
|
22
22
|
from ..langchain.chat_message_template import Jinja2TemplatedChatMessagesTemplate
|
|
23
|
-
from ..utils.utils import TOOLKIT_SPLITTER
|
|
24
23
|
from ..utils.mcp_oauth import McpAuthorizationRequired
|
|
25
|
-
from ...tools import get_available_toolkit_models
|
|
24
|
+
from ...tools import get_available_toolkit_models, instantiate_toolkit
|
|
26
25
|
from ...tools.base_indexer_toolkit import IndexTools
|
|
27
26
|
|
|
28
27
|
logger = logging.getLogger(__name__)
|
|
@@ -146,6 +145,19 @@ class AlitaClient:
|
|
|
146
145
|
data = requests.get(url, headers=self.headers, verify=False).json()
|
|
147
146
|
return data
|
|
148
147
|
|
|
148
|
+
def toolkit(self, toolkit_id: int):
|
|
149
|
+
url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
|
|
150
|
+
response = requests.get(url, headers=self.headers, verify=False)
|
|
151
|
+
if not response.ok:
|
|
152
|
+
raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
|
|
153
|
+
|
|
154
|
+
tool_data = response.json()
|
|
155
|
+
if 'settings' not in tool_data:
|
|
156
|
+
tool_data['settings'] = {}
|
|
157
|
+
tool_data['settings']['alita'] = self
|
|
158
|
+
|
|
159
|
+
return instantiate_toolkit(tool_data)
|
|
160
|
+
|
|
149
161
|
def get_list_of_apps(self):
|
|
150
162
|
apps = []
|
|
151
163
|
limit = 10
|
|
@@ -348,7 +360,7 @@ class AlitaClient:
|
|
|
348
360
|
application_variables: Optional[dict] = None,
|
|
349
361
|
version_details: Optional[dict] = None, store: Optional[BaseStore] = None,
|
|
350
362
|
llm: Optional[ChatOpenAI] = None, mcp_tokens: Optional[dict] = None,
|
|
351
|
-
conversation_id: Optional[str] = None):
|
|
363
|
+
conversation_id: Optional[str] = None, ignored_mcp_servers: Optional[list] = None):
|
|
352
364
|
if tools is None:
|
|
353
365
|
tools = []
|
|
354
366
|
if chat_history is None:
|
|
@@ -397,12 +409,12 @@ class AlitaClient:
|
|
|
397
409
|
if runtime == 'nonrunnable':
|
|
398
410
|
return LangChainAssistant(self, data, llm, chat_history, app_type,
|
|
399
411
|
tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
|
|
400
|
-
conversation_id=conversation_id)
|
|
412
|
+
conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers)
|
|
401
413
|
if runtime == 'langchain':
|
|
402
414
|
return LangChainAssistant(self, data, llm,
|
|
403
415
|
chat_history, app_type,
|
|
404
416
|
tools=tools, memory=memory, store=store, mcp_tokens=mcp_tokens,
|
|
405
|
-
conversation_id=conversation_id).runnable()
|
|
417
|
+
conversation_id=conversation_id, ignored_mcp_servers=ignored_mcp_servers).runnable()
|
|
406
418
|
elif runtime == 'llama':
|
|
407
419
|
raise NotImplementedError("LLama runtime is not supported")
|
|
408
420
|
|
|
@@ -656,7 +668,8 @@ class AlitaClient:
|
|
|
656
668
|
tools: Optional[list] = None, chat_history: Optional[List[Any]] = None,
|
|
657
669
|
memory=None, runtime='langchain', variables: Optional[list] = None,
|
|
658
670
|
store: Optional[BaseStore] = None, debug_mode: Optional[bool] = False,
|
|
659
|
-
mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None
|
|
671
|
+
mcp_tokens: Optional[dict] = None, conversation_id: Optional[str] = None,
|
|
672
|
+
ignored_mcp_servers: Optional[list] = None):
|
|
660
673
|
"""
|
|
661
674
|
Create a predict-type agent with minimal configuration.
|
|
662
675
|
|
|
@@ -672,6 +685,7 @@ class AlitaClient:
|
|
|
672
685
|
variables: Optional list of variables for the agent
|
|
673
686
|
store: Optional store for memory
|
|
674
687
|
debug_mode: Enable debug mode for cases when assistant can be initialized without tools
|
|
688
|
+
ignored_mcp_servers: Optional list of MCP server URLs to ignore (user chose to continue without auth)
|
|
675
689
|
|
|
676
690
|
Returns:
|
|
677
691
|
Runnable agent ready for execution
|
|
@@ -705,7 +719,8 @@ class AlitaClient:
|
|
|
705
719
|
store=store,
|
|
706
720
|
debug_mode=debug_mode,
|
|
707
721
|
mcp_tokens=mcp_tokens,
|
|
708
|
-
conversation_id=conversation_id
|
|
722
|
+
conversation_id=conversation_id,
|
|
723
|
+
ignored_mcp_servers=ignored_mcp_servers
|
|
709
724
|
).runnable()
|
|
710
725
|
|
|
711
726
|
def test_toolkit_tool(self, toolkit_config: dict, tool_name: str, tool_params: dict = None,
|
|
@@ -939,7 +954,6 @@ class AlitaClient:
|
|
|
939
954
|
if target_tool is None:
|
|
940
955
|
available_tools = []
|
|
941
956
|
base_available_tools = []
|
|
942
|
-
full_available_tools = []
|
|
943
957
|
|
|
944
958
|
for tool in tools:
|
|
945
959
|
tool_name_attr = None
|
|
@@ -956,10 +970,6 @@ class AlitaClient:
|
|
|
956
970
|
if base_name not in base_available_tools:
|
|
957
971
|
base_available_tools.append(base_name)
|
|
958
972
|
|
|
959
|
-
# Track full names separately
|
|
960
|
-
if TOOLKIT_SPLITTER in tool_name_attr:
|
|
961
|
-
full_available_tools.append(tool_name_attr)
|
|
962
|
-
|
|
963
973
|
# Create comprehensive error message
|
|
964
974
|
error_msg = f"Tool '{tool_name}' not found in toolkit '{toolkit_config.get('toolkit_name')}'.\n"
|
|
965
975
|
|
|
@@ -967,9 +977,7 @@ class AlitaClient:
|
|
|
967
977
|
if toolkit_name in [tool.value for tool in IndexTools]:
|
|
968
978
|
error_msg += f" Please make sure proper PGVector configuration and embedding model are set in the platform.\n"
|
|
969
979
|
|
|
970
|
-
if base_available_tools
|
|
971
|
-
error_msg += f" Available tools: {base_available_tools} (base names) or {full_available_tools} (full names)"
|
|
972
|
-
elif base_available_tools:
|
|
980
|
+
if base_available_tools:
|
|
973
981
|
error_msg += f" Available tools: {base_available_tools}"
|
|
974
982
|
elif available_tools:
|
|
975
983
|
error_msg += f" Available tools: {available_tools}"
|
|
@@ -978,10 +986,7 @@ class AlitaClient:
|
|
|
978
986
|
|
|
979
987
|
# Add helpful hint about naming conventions
|
|
980
988
|
if '___' in tool_name:
|
|
981
|
-
error_msg += f" Note:
|
|
982
|
-
elif full_available_tools:
|
|
983
|
-
possible_full_name = create_full_tool_name(tool_name, toolkit_name)
|
|
984
|
-
error_msg += f" Note: You provided a base name '{tool_name}'. The full name might be '{possible_full_name}'."
|
|
989
|
+
error_msg += f" Note: Tool names no longer use '___' prefixes. Try using just the base name '{extract_base_tool_name(tool_name)}'."
|
|
985
990
|
|
|
986
991
|
return {
|
|
987
992
|
"success": False,
|
|
@@ -6,6 +6,7 @@ import requests
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
from json import dumps
|
|
8
8
|
import chardet
|
|
9
|
+
from ...tools import instantiate_toolkit
|
|
9
10
|
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
11
12
|
|
|
@@ -184,6 +185,19 @@ class SandboxClient:
|
|
|
184
185
|
data = requests.get(url, headers=self.headers, verify=False).json()
|
|
185
186
|
return data
|
|
186
187
|
|
|
188
|
+
def toolkit(self, toolkit_id: int):
|
|
189
|
+
url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
|
|
190
|
+
response = requests.get(url, headers=self.headers, verify=False)
|
|
191
|
+
if not response.ok:
|
|
192
|
+
raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
|
|
193
|
+
|
|
194
|
+
tool_data = response.json()
|
|
195
|
+
if 'settings' not in tool_data:
|
|
196
|
+
tool_data['settings'] = {}
|
|
197
|
+
tool_data['settings']['alita'] = self
|
|
198
|
+
|
|
199
|
+
return instantiate_toolkit(tool_data)
|
|
200
|
+
|
|
187
201
|
def get_list_of_apps(self):
|
|
188
202
|
apps = []
|
|
189
203
|
limit = 10
|
|
@@ -33,7 +33,8 @@ class Assistant:
|
|
|
33
33
|
store: Optional[BaseStore] = None,
|
|
34
34
|
debug_mode: Optional[bool] = False,
|
|
35
35
|
mcp_tokens: Optional[dict] = None,
|
|
36
|
-
conversation_id: Optional[str] = None
|
|
36
|
+
conversation_id: Optional[str] = None,
|
|
37
|
+
ignored_mcp_servers: Optional[list] = None):
|
|
37
38
|
|
|
38
39
|
self.app_type = app_type
|
|
39
40
|
self.memory = memory
|
|
@@ -98,10 +99,55 @@ class Assistant:
|
|
|
98
99
|
memory_store=self.store,
|
|
99
100
|
debug_mode=debug_mode,
|
|
100
101
|
mcp_tokens=mcp_tokens,
|
|
101
|
-
conversation_id=conversation_id
|
|
102
|
+
conversation_id=conversation_id,
|
|
103
|
+
ignored_mcp_servers=ignored_mcp_servers
|
|
102
104
|
)
|
|
103
105
|
if tools:
|
|
104
106
|
self.tools += tools
|
|
107
|
+
|
|
108
|
+
# Create ToolRegistry to track tool metadata and handle name collisions
|
|
109
|
+
self.tool_registry = {}
|
|
110
|
+
tool_name_counts = {} # Track how many times each base name appears
|
|
111
|
+
|
|
112
|
+
for tool in self.tools:
|
|
113
|
+
if hasattr(tool, 'name'):
|
|
114
|
+
original_name = tool.name
|
|
115
|
+
base_name = original_name
|
|
116
|
+
|
|
117
|
+
# Extract toolkit metadata from tool configuration
|
|
118
|
+
toolkit_name = ""
|
|
119
|
+
toolkit_type = ""
|
|
120
|
+
|
|
121
|
+
# Find matching tool config to extract metadata
|
|
122
|
+
for tool_config in version_tools:
|
|
123
|
+
# Try to match by toolkit_name or name field
|
|
124
|
+
config_toolkit_name = tool_config.get('toolkit_name', tool_config.get('name', ''))
|
|
125
|
+
# Simple heuristic: toolkit info should be accessible from tool config
|
|
126
|
+
# For now, use toolkit_name and type from config
|
|
127
|
+
toolkit_name = config_toolkit_name
|
|
128
|
+
toolkit_type = tool_config.get('type', '')
|
|
129
|
+
break # Use first match for now; will refine with better matching
|
|
130
|
+
|
|
131
|
+
# Handle duplicate tool names by appending numeric suffix
|
|
132
|
+
if base_name in tool_name_counts:
|
|
133
|
+
tool_name_counts[base_name] += 1
|
|
134
|
+
# Append suffix to make unique
|
|
135
|
+
new_name = f"{base_name}_{tool_name_counts[base_name]}"
|
|
136
|
+
tool.name = new_name
|
|
137
|
+
logger.info(f"Tool name collision detected: '{base_name}' -> '{new_name}'")
|
|
138
|
+
else:
|
|
139
|
+
tool_name_counts[base_name] = 0
|
|
140
|
+
new_name = base_name
|
|
141
|
+
|
|
142
|
+
# Store in registry
|
|
143
|
+
self.tool_registry[tool.name] = {
|
|
144
|
+
'toolkit_name': toolkit_name,
|
|
145
|
+
'toolkit_type': toolkit_type,
|
|
146
|
+
'original_tool_name': base_name
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
logger.info(f"ToolRegistry initialized with {len(self.tool_registry)} tools")
|
|
150
|
+
|
|
105
151
|
# Handle prompt setup
|
|
106
152
|
if app_type in ["pipeline", "predict", "react"]:
|
|
107
153
|
self.prompt = data['instructions']
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from .AlitaJSONLoader import AlitaJSONLoader
|
|
2
|
+
import json
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from typing import List, Iterator
|
|
5
|
+
|
|
6
|
+
from langchain_core.documents import Document
|
|
7
|
+
from langchain_core.tools import ToolException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlitaJSONLinesLoader(AlitaJSONLoader):
|
|
11
|
+
"""Load local JSONL files (one JSON object per line) using AlitaJSONLoader behavior.
|
|
12
|
+
|
|
13
|
+
Behavior:
|
|
14
|
+
- Supports both `file_path` and `file_content` (bytes or file-like object), same as AlitaJSONLoader.
|
|
15
|
+
- Treats each non-empty line as an independent JSON object.
|
|
16
|
+
- Aggregates all parsed JSON objects into a list and feeds them through the same
|
|
17
|
+
RecursiveJsonSplitter-based chunking used by AlitaJSONLoader.lazy_load.
|
|
18
|
+
- Returns a list of Documents with chunked JSON content.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, **kwargs):
|
|
22
|
+
# Reuse AlitaJSONLoader initialization logic (file_path / file_content handling, encoding, etc.)
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
|
|
25
|
+
def _iter_lines(self) -> Iterator[str]:
|
|
26
|
+
"""Yield lines from file_path or file_content, mirroring AlitaJSONLoader sources."""
|
|
27
|
+
# Prefer file_path if available
|
|
28
|
+
if hasattr(self, "file_path") and self.file_path:
|
|
29
|
+
with open(self.file_path, "r", encoding=self.encoding) as f:
|
|
30
|
+
for line in f:
|
|
31
|
+
yield line
|
|
32
|
+
# Fallback to file_content if available
|
|
33
|
+
elif hasattr(self, "file_content") and self.file_content:
|
|
34
|
+
# file_content may be bytes or a file-like object
|
|
35
|
+
if isinstance(self.file_content, (bytes, bytearray)):
|
|
36
|
+
text = self.file_content.decode(self.encoding)
|
|
37
|
+
for line in StringIO(text):
|
|
38
|
+
yield line
|
|
39
|
+
else:
|
|
40
|
+
# Assume it's a text file-like object positioned at the beginning
|
|
41
|
+
self.file_content.seek(0)
|
|
42
|
+
for line in self.file_content:
|
|
43
|
+
yield line
|
|
44
|
+
else:
|
|
45
|
+
raise ToolException("'file_path' or 'file_content' parameter should be provided.")
|
|
46
|
+
|
|
47
|
+
def load(self) -> List[Document]: # type: ignore[override]
|
|
48
|
+
"""Load JSONL content by delegating each non-empty line to AlitaJSONLoader.
|
|
49
|
+
|
|
50
|
+
For each non-empty line in the underlying source (file_path or file_content):
|
|
51
|
+
- Create a temporary AlitaJSONLoader instance with that line as file_content.
|
|
52
|
+
- Call lazy_load() on that instance to apply the same RecursiveJsonSplitter logic
|
|
53
|
+
as for a normal JSON file.
|
|
54
|
+
- Accumulate all Documents from all lines and return them as a single list.
|
|
55
|
+
"""
|
|
56
|
+
docs: List[Document] = []
|
|
57
|
+
|
|
58
|
+
for raw_line in self._iter_lines():
|
|
59
|
+
line = raw_line.strip()
|
|
60
|
+
if not line:
|
|
61
|
+
continue
|
|
62
|
+
try:
|
|
63
|
+
# Instantiate a per-line AlitaJSONLoader using the same configuration
|
|
64
|
+
line_loader = AlitaJSONLoader(
|
|
65
|
+
file_content=line,
|
|
66
|
+
file_name=getattr(self, "file_name", str(getattr(self, "file_path", "no_name"))),
|
|
67
|
+
encoding=self.encoding,
|
|
68
|
+
autodetect_encoding=self.autodetect_encoding,
|
|
69
|
+
max_tokens=self.max_tokens,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
for doc in line_loader.lazy_load():
|
|
73
|
+
docs.append(doc)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise ToolException(f"Error processing JSONL line: {line[:100]}... Error: {e}") from e
|
|
76
|
+
|
|
77
|
+
return docs
|
|
@@ -32,6 +32,8 @@ class AlitaJSONLoader(BaseLoader):
|
|
|
32
32
|
elif hasattr(self, 'file_content') and self.file_content:
|
|
33
33
|
if isinstance(self.file_content, bytes):
|
|
34
34
|
return json.loads(self.file_content.decode(self.encoding))
|
|
35
|
+
elif isinstance(self.file_content, str):
|
|
36
|
+
return json.loads(self.file_content)
|
|
35
37
|
else:
|
|
36
38
|
return json.load(self.file_content)
|
|
37
39
|
else:
|
|
@@ -45,7 +47,6 @@ class AlitaJSONLoader(BaseLoader):
|
|
|
45
47
|
try:
|
|
46
48
|
with open(self.file_path, encoding=encoding.encoding) as f:
|
|
47
49
|
return f.read()
|
|
48
|
-
break
|
|
49
50
|
except UnicodeDecodeError:
|
|
50
51
|
continue
|
|
51
52
|
elif hasattr(self, 'file_content') and self.file_content:
|
|
@@ -21,6 +21,7 @@ from .AlitaDocxMammothLoader import AlitaDocxMammothLoader
|
|
|
21
21
|
from .AlitaExcelLoader import AlitaExcelLoader
|
|
22
22
|
from .AlitaImageLoader import AlitaImageLoader
|
|
23
23
|
from .AlitaJSONLoader import AlitaJSONLoader
|
|
24
|
+
from .AlitaJSONLinesLoader import AlitaJSONLinesLoader
|
|
24
25
|
from .AlitaPDFLoader import AlitaPDFLoader
|
|
25
26
|
from .AlitaPowerPointLoader import AlitaPowerPointLoader
|
|
26
27
|
from .AlitaTextLoader import AlitaTextLoader
|
|
@@ -208,7 +209,7 @@ document_loaders_map = {
|
|
|
208
209
|
'allowed_to_override': DEFAULT_ALLOWED_BASE
|
|
209
210
|
},
|
|
210
211
|
'.jsonl': {
|
|
211
|
-
'class':
|
|
212
|
+
'class': AlitaJSONLinesLoader,
|
|
212
213
|
'mime_type': 'application/jsonl',
|
|
213
214
|
'is_multimodal_processing': False,
|
|
214
215
|
'kwargs': {},
|
|
@@ -30,7 +30,7 @@ from ..tools.loop import LoopNode
|
|
|
30
30
|
from ..tools.loop_output import LoopToolNode
|
|
31
31
|
from ..tools.tool import ToolNode
|
|
32
32
|
from ..utils.evaluate import EvaluateTemplate
|
|
33
|
-
from ..utils.utils import clean_string
|
|
33
|
+
from ..utils.utils import clean_string
|
|
34
34
|
from ..tools.router import RouterNode
|
|
35
35
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
@@ -191,7 +191,7 @@ Answer only with step name, no need to add descrip in case none of the steps are
|
|
|
191
191
|
additional_info = """### Additoinal info: """
|
|
192
192
|
additional_info += "{field}: {value}\n".format(field=field, value=state.get(field, ""))
|
|
193
193
|
decision_input.append(HumanMessage(
|
|
194
|
-
self.prompt.format(steps=self.steps, description=self.description, additional_info=additional_info)))
|
|
194
|
+
self.prompt.format(steps=self.steps, description=safe_format(self.description, state), additional_info=additional_info)))
|
|
195
195
|
completion = self.client.invoke(decision_input)
|
|
196
196
|
result = clean_string(completion.content.strip())
|
|
197
197
|
logger.info(f"Plan to transition to: {result}")
|
|
@@ -483,8 +483,7 @@ def create_graph(
|
|
|
483
483
|
node_id = clean_string(node['id'])
|
|
484
484
|
toolkit_name = node.get('toolkit_name')
|
|
485
485
|
tool_name = clean_string(node.get('tool', node_id))
|
|
486
|
-
|
|
487
|
-
tool_name = f"{clean_string(toolkit_name)}{TOOLKIT_SPLITTER}{tool_name}"
|
|
486
|
+
# Tool names are now clean (no prefix needed)
|
|
488
487
|
logger.info(f"Node: {node_id} : {node_type} - {tool_name}")
|
|
489
488
|
if node_type in ['function', 'toolkit', 'mcp', 'tool', 'loop', 'loop_from_tool', 'indexer', 'subgraph', 'pipeline', 'agent']:
|
|
490
489
|
if node_type == 'mcp' and tool_name not in [tool.name for tool in tools]:
|
|
@@ -550,8 +549,8 @@ def create_graph(
|
|
|
550
549
|
loop_toolkit_name = node.get('loop_toolkit_name')
|
|
551
550
|
loop_tool_name = node.get('loop_tool')
|
|
552
551
|
if (loop_toolkit_name and loop_tool_name) or loop_tool_name:
|
|
553
|
-
|
|
554
|
-
|
|
552
|
+
# Use clean tool name (no prefix)
|
|
553
|
+
loop_tool_name = clean_string(loop_tool_name)
|
|
555
554
|
for t in tools:
|
|
556
555
|
if t.name == loop_tool_name:
|
|
557
556
|
logger.debug(f"Loop tool discovered: {t}")
|
|
@@ -609,10 +608,10 @@ def create_graph(
|
|
|
609
608
|
tool_names = []
|
|
610
609
|
if isinstance(connected_tools, dict):
|
|
611
610
|
for toolkit, selected_tools in connected_tools.items():
|
|
612
|
-
|
|
613
|
-
|
|
611
|
+
# Add tool names directly (no prefix)
|
|
612
|
+
tool_names.extend(selected_tools)
|
|
614
613
|
elif isinstance(connected_tools, list):
|
|
615
|
-
#
|
|
614
|
+
# Use provided tool names as-is
|
|
616
615
|
tool_names = connected_tools
|
|
617
616
|
|
|
618
617
|
if tool_names:
|
|
@@ -208,7 +208,12 @@ def safe_format(template, mapping):
|
|
|
208
208
|
def create_pydantic_model(model_name: str, variables: dict[str, dict]):
|
|
209
209
|
fields = {}
|
|
210
210
|
for var_name, var_data in variables.items():
|
|
211
|
-
|
|
211
|
+
if 'default' in var_data:
|
|
212
|
+
# allow user to define if it is required or not
|
|
213
|
+
fields[var_name] = (parse_pydantic_type(var_data['type']),
|
|
214
|
+
Field(description=var_data.get('description', None), default=var_data.get('default')))
|
|
215
|
+
else:
|
|
216
|
+
fields[var_name] = (parse_pydantic_type(var_data['type']), Field(description=var_data.get('description', None)))
|
|
212
217
|
return create_model(model_name, **fields)
|
|
213
218
|
|
|
214
219
|
def parse_pydantic_type(type_name: str):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import List, Any, Literal, Optional
|
|
2
2
|
|
|
3
|
-
from alita_sdk.tools.utils import clean_string,
|
|
3
|
+
from alita_sdk.tools.utils import clean_string, get_max_toolkit_length
|
|
4
|
+
from alita_sdk.tools.elitea_base import filter_missconfigured_index_tools
|
|
4
5
|
from langchain_community.agent_toolkits.base import BaseToolkit
|
|
5
6
|
from langchain_core.tools import BaseTool
|
|
6
7
|
from pydantic import create_model, BaseModel, ConfigDict, Field
|
|
@@ -40,26 +41,34 @@ class ArtifactToolkit(BaseToolkit):
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
@classmethod
|
|
44
|
+
@filter_missconfigured_index_tools
|
|
43
45
|
def get_toolkit(cls, client: Any, bucket: str, toolkit_name: Optional[str] = None, selected_tools: list[str] = [], **kwargs):
|
|
44
46
|
if selected_tools is None:
|
|
45
47
|
selected_tools = []
|
|
48
|
+
|
|
46
49
|
tools = []
|
|
47
50
|
wrapper_payload = {
|
|
48
51
|
**kwargs,
|
|
49
52
|
**(kwargs.get('pgvector_configuration') or {}),
|
|
50
53
|
}
|
|
51
54
|
artifact_wrapper = ArtifactWrapper(alita=client, bucket=bucket, **wrapper_payload)
|
|
52
|
-
|
|
55
|
+
# Use clean toolkit name for context (max 1000 chars in description)
|
|
56
|
+
toolkit_context = f" [Toolkit: {clean_string(toolkit_name, 0)}]" if toolkit_name else ''
|
|
53
57
|
available_tools = artifact_wrapper.get_available_tools()
|
|
54
58
|
for tool in available_tools:
|
|
55
59
|
if selected_tools:
|
|
56
60
|
if tool["name"] not in selected_tools:
|
|
57
61
|
continue
|
|
62
|
+
# Add toolkit context to description with character limit
|
|
63
|
+
description = tool["description"]
|
|
64
|
+
if toolkit_context and len(description + toolkit_context) <= 1000:
|
|
65
|
+
description = description + toolkit_context
|
|
58
66
|
tools.append(BaseAction(
|
|
59
67
|
api_wrapper=artifact_wrapper,
|
|
60
|
-
name=
|
|
61
|
-
description=
|
|
62
|
-
args_schema=tool["args_schema"]
|
|
68
|
+
name=tool["name"],
|
|
69
|
+
description=description,
|
|
70
|
+
args_schema=tool["args_schema"],
|
|
71
|
+
metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
|
|
63
72
|
))
|
|
64
73
|
return cls(tools=tools)
|
|
65
74
|
|
|
@@ -3,7 +3,7 @@ from pydantic import create_model, BaseModel, Field
|
|
|
3
3
|
from langchain_community.agent_toolkits.base import BaseToolkit
|
|
4
4
|
from langchain_core.tools import BaseTool, ToolException
|
|
5
5
|
from ..tools.datasource import DatasourcePredict, DatasourceSearch, datasourceToolSchema
|
|
6
|
-
from alita_sdk.tools.utils import clean_string
|
|
6
|
+
from alita_sdk.tools.utils import clean_string
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class DatasourcesToolkit(BaseToolkit):
|
|
@@ -21,21 +21,28 @@ class DatasourcesToolkit(BaseToolkit):
|
|
|
21
21
|
@classmethod
|
|
22
22
|
def get_toolkit(cls, client: Any, datasource_ids: list[int], toolkit_name: Optional[str] = None, selected_tools: list[str] = []):
|
|
23
23
|
tools = []
|
|
24
|
-
|
|
24
|
+
# Use clean toolkit name for context (max 1000 chars in description)
|
|
25
|
+
toolkit_context = f" [Toolkit: {clean_string(toolkit_name)}]" if toolkit_name else ''
|
|
25
26
|
for datasource_id in datasource_ids:
|
|
26
27
|
datasource = client.datasource(datasource_id)
|
|
27
28
|
ds_name = clean_string(datasource.name)
|
|
28
29
|
if len(ds_name) == 0:
|
|
29
30
|
raise ToolException(f'Datasource with id {datasource_id} has incorrect name (i.e. special characters, etc.)')
|
|
30
31
|
if len(selected_tools) == 0 or 'chat' in selected_tools:
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
description = f'Search and summarize. {datasource.description}'
|
|
33
|
+
if toolkit_context and len(description + toolkit_context) <= 1000:
|
|
34
|
+
description = description + toolkit_context
|
|
35
|
+
tools.append(DatasourcePredict(name=f'chat',
|
|
36
|
+
description=description,
|
|
33
37
|
datasource=datasource,
|
|
34
38
|
args_schema=datasourceToolSchema,
|
|
35
39
|
return_type='str'))
|
|
36
40
|
if len(selected_tools) == 0 or 'search' in selected_tools:
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
description = f'Search return results. {datasource.description}'
|
|
42
|
+
if toolkit_context and len(description + toolkit_context) <= 1000:
|
|
43
|
+
description = description + toolkit_context
|
|
44
|
+
tools.append(DatasourceSearch(name=f'search',
|
|
45
|
+
description=description,
|
|
39
46
|
datasource=datasource,
|
|
40
47
|
args_schema=datasourceToolSchema,
|
|
41
48
|
return_type='str'))
|