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.

Files changed (108) hide show
  1. alita_sdk/cli/inventory.py +12 -195
  2. alita_sdk/community/inventory/__init__.py +12 -0
  3. alita_sdk/community/inventory/toolkit.py +9 -5
  4. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  5. alita_sdk/configurations/ado.py +144 -0
  6. alita_sdk/configurations/confluence.py +76 -42
  7. alita_sdk/configurations/figma.py +76 -0
  8. alita_sdk/configurations/gitlab.py +2 -0
  9. alita_sdk/configurations/qtest.py +72 -1
  10. alita_sdk/configurations/report_portal.py +96 -0
  11. alita_sdk/configurations/sharepoint.py +148 -0
  12. alita_sdk/configurations/testio.py +83 -0
  13. alita_sdk/runtime/clients/artifact.py +2 -2
  14. alita_sdk/runtime/clients/client.py +24 -19
  15. alita_sdk/runtime/clients/sandbox_client.py +14 -0
  16. alita_sdk/runtime/langchain/assistant.py +48 -2
  17. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  18. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +2 -1
  19. alita_sdk/runtime/langchain/document_loaders/constants.py +2 -1
  20. alita_sdk/runtime/langchain/langraph_agent.py +8 -9
  21. alita_sdk/runtime/langchain/utils.py +6 -1
  22. alita_sdk/runtime/toolkits/artifact.py +14 -5
  23. alita_sdk/runtime/toolkits/datasource.py +13 -6
  24. alita_sdk/runtime/toolkits/mcp.py +26 -157
  25. alita_sdk/runtime/toolkits/planning.py +10 -5
  26. alita_sdk/runtime/toolkits/tools.py +23 -7
  27. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  28. alita_sdk/runtime/tools/artifact.py +139 -6
  29. alita_sdk/runtime/tools/llm.py +20 -10
  30. alita_sdk/runtime/tools/mcp_remote_tool.py +2 -3
  31. alita_sdk/runtime/tools/mcp_server_tool.py +2 -4
  32. alita_sdk/runtime/utils/AlitaCallback.py +30 -1
  33. alita_sdk/runtime/utils/mcp_client.py +33 -6
  34. alita_sdk/runtime/utils/mcp_oauth.py +125 -8
  35. alita_sdk/runtime/utils/mcp_sse_client.py +35 -6
  36. alita_sdk/runtime/utils/utils.py +2 -0
  37. alita_sdk/tools/__init__.py +15 -0
  38. alita_sdk/tools/ado/repos/__init__.py +10 -12
  39. alita_sdk/tools/ado/test_plan/__init__.py +23 -8
  40. alita_sdk/tools/ado/wiki/__init__.py +24 -8
  41. alita_sdk/tools/ado/wiki/ado_wrapper.py +21 -7
  42. alita_sdk/tools/ado/work_item/__init__.py +24 -8
  43. alita_sdk/tools/advanced_jira_mining/__init__.py +10 -8
  44. alita_sdk/tools/aws/delta_lake/__init__.py +12 -9
  45. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  46. alita_sdk/tools/azure_ai/search/__init__.py +9 -7
  47. alita_sdk/tools/base/tool.py +5 -1
  48. alita_sdk/tools/base_indexer_toolkit.py +25 -0
  49. alita_sdk/tools/bitbucket/__init__.py +14 -10
  50. alita_sdk/tools/bitbucket/api_wrapper.py +50 -2
  51. alita_sdk/tools/browser/__init__.py +5 -4
  52. alita_sdk/tools/carrier/__init__.py +5 -6
  53. alita_sdk/tools/cloud/aws/__init__.py +9 -7
  54. alita_sdk/tools/cloud/azure/__init__.py +9 -7
  55. alita_sdk/tools/cloud/gcp/__init__.py +9 -7
  56. alita_sdk/tools/cloud/k8s/__init__.py +9 -7
  57. alita_sdk/tools/code/linter/__init__.py +9 -8
  58. alita_sdk/tools/code/sonar/__init__.py +9 -7
  59. alita_sdk/tools/confluence/__init__.py +15 -10
  60. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  61. alita_sdk/tools/elastic/__init__.py +10 -8
  62. alita_sdk/tools/elitea_base.py +387 -9
  63. alita_sdk/tools/figma/__init__.py +8 -7
  64. alita_sdk/tools/github/__init__.py +12 -14
  65. alita_sdk/tools/github/github_client.py +68 -2
  66. alita_sdk/tools/github/tool.py +5 -1
  67. alita_sdk/tools/gitlab/__init__.py +14 -11
  68. alita_sdk/tools/gitlab/api_wrapper.py +81 -1
  69. alita_sdk/tools/gitlab_org/__init__.py +9 -8
  70. alita_sdk/tools/google/bigquery/__init__.py +12 -12
  71. alita_sdk/tools/google/bigquery/tool.py +5 -1
  72. alita_sdk/tools/google_places/__init__.py +9 -8
  73. alita_sdk/tools/jira/__init__.py +15 -10
  74. alita_sdk/tools/keycloak/__init__.py +10 -8
  75. alita_sdk/tools/localgit/__init__.py +8 -3
  76. alita_sdk/tools/localgit/local_git.py +62 -54
  77. alita_sdk/tools/localgit/tool.py +5 -1
  78. alita_sdk/tools/memory/__init__.py +11 -3
  79. alita_sdk/tools/ocr/__init__.py +10 -8
  80. alita_sdk/tools/openapi/__init__.py +6 -2
  81. alita_sdk/tools/pandas/__init__.py +9 -7
  82. alita_sdk/tools/postman/__init__.py +10 -11
  83. alita_sdk/tools/pptx/__init__.py +9 -9
  84. alita_sdk/tools/qtest/__init__.py +9 -8
  85. alita_sdk/tools/rally/__init__.py +9 -8
  86. alita_sdk/tools/report_portal/__init__.py +11 -9
  87. alita_sdk/tools/salesforce/__init__.py +9 -9
  88. alita_sdk/tools/servicenow/__init__.py +10 -8
  89. alita_sdk/tools/sharepoint/__init__.py +9 -8
  90. alita_sdk/tools/slack/__init__.py +8 -7
  91. alita_sdk/tools/sql/__init__.py +9 -8
  92. alita_sdk/tools/testio/__init__.py +9 -8
  93. alita_sdk/tools/testrail/__init__.py +10 -8
  94. alita_sdk/tools/utils/__init__.py +9 -4
  95. alita_sdk/tools/utils/text_operations.py +254 -0
  96. alita_sdk/tools/xray/__init__.py +10 -8
  97. alita_sdk/tools/yagmail/__init__.py +8 -3
  98. alita_sdk/tools/zephyr/__init__.py +8 -7
  99. alita_sdk/tools/zephyr_enterprise/__init__.py +10 -8
  100. alita_sdk/tools/zephyr_essential/__init__.py +9 -8
  101. alita_sdk/tools/zephyr_scale/__init__.py +9 -8
  102. alita_sdk/tools/zephyr_squad/__init__.py +9 -8
  103. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/METADATA +1 -1
  104. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/RECORD +108 -105
  105. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/WHEEL +0 -0
  106. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/entry_points.txt +0 -0
  107. {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.515.dist-info}/licenses/LICENSE +0 -0
  108. {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['error']:
42
- return f"{data['error']}. {data['content'] if data['content'] else ''}"
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 and full_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: You provided a full name '{tool_name}'. Try using just the base name '{extract_base_tool_name(tool_name)}'."
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': AirbyteJSONLoader,
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, TOOLKIT_SPLITTER
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
- if toolkit_name:
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
- loop_tool_name = f"{clean_string(loop_toolkit_name)}{TOOLKIT_SPLITTER}{loop_tool_name}" if loop_toolkit_name else clean_string(
554
- loop_tool_name)
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
- for tool in selected_tools:
613
- tool_names.append(f"{toolkit}{TOOLKIT_SPLITTER}{tool}")
611
+ # Add tool names directly (no prefix)
612
+ tool_names.extend(selected_tools)
614
613
  elif isinstance(connected_tools, list):
615
- # for cases when tools are provided as a list of names with already bound toolkit_name
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
- fields[var_name] = (parse_pydantic_type(var_data['type']), Field(description=var_data.get('description', None)))
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, TOOLKIT_SPLITTER, get_max_toolkit_length
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
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
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=prefix + tool["name"],
61
- description=tool["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, TOOLKIT_SPLITTER
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
- prefix = clean_string(toolkit_name) + TOOLKIT_SPLITTER if toolkit_name else ''
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
- tools.append(DatasourcePredict(name=f'{prefix}chat',
32
- description=f'Search and summarize. {datasource.description}',
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
- tools.append(DatasourceSearch(name=f'{prefix}search',
38
- description=f'Search return results. {datasource.description}',
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'))