alita-sdk 0.3.497__py3-none-any.whl → 0.3.516__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 +64 -23
- alita_sdk/runtime/langchain/constants.py +270 -1
- 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.516.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/RECORD +109 -106
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.497.dist-info → alita_sdk-0.3.516.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
|
|
@@ -13,7 +13,7 @@ from langchain_core.messages import (
|
|
|
13
13
|
BaseMessage, SystemMessage, HumanMessage
|
|
14
14
|
)
|
|
15
15
|
from langchain_core.prompts import MessagesPlaceholder
|
|
16
|
-
from .constants import REACT_ADDON, REACT_VARS, XML_ADDON
|
|
16
|
+
from .constants import REACT_ADDON, REACT_VARS, XML_ADDON, USER_ADDON, DEFAULT_ASSISTANT, PLAN_ADDON, PYODITE_ADDON
|
|
17
17
|
from .chat_message_template import Jinja2TemplatedChatMessagesTemplate
|
|
18
18
|
from ..tools.echo import EchoTool
|
|
19
19
|
from langchain_core.tools import BaseTool, ToolException
|
|
@@ -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']
|
|
@@ -230,34 +276,29 @@ class Assistant:
|
|
|
230
276
|
# Only use prompt_instructions if explicitly specified (for predict app_type)
|
|
231
277
|
if self.app_type == "predict" and isinstance(self.prompt, str):
|
|
232
278
|
prompt_instructions = self.prompt
|
|
233
|
-
|
|
234
|
-
# take the system message from the openai prompt as a prompt instructions
|
|
235
|
-
if self.app_type == "openai" and hasattr(self.prompt, 'messages'):
|
|
236
|
-
prompt_instructions = self.__take_prompt_from_openai_messages()
|
|
237
|
-
|
|
238
|
-
# Create a unified YAML schema with conditional tool binding
|
|
239
|
-
# Build the base node configuration
|
|
240
|
-
node_config = {
|
|
241
|
-
'id': 'agent',
|
|
242
|
-
'type': 'llm',
|
|
243
|
-
'prompt': {
|
|
244
|
-
'template': prompt_instructions or "You are a helpful assistant."
|
|
245
|
-
},
|
|
246
|
-
'input': ['messages'],
|
|
247
|
-
'output': ['messages'],
|
|
248
|
-
'transition': 'END'
|
|
249
|
-
}
|
|
250
279
|
|
|
251
280
|
# Add tool binding only if tools are present
|
|
252
281
|
if simple_tools:
|
|
253
282
|
tool_names = [tool.name for tool in simple_tools]
|
|
254
|
-
tool_names_yaml = str(tool_names).replace("'", '"') # Convert to YAML-compatible format
|
|
255
|
-
node_config['tool_names'] = tool_names_yaml
|
|
256
283
|
logger.info("Binding tools: %s", tool_names)
|
|
257
284
|
|
|
285
|
+
# take the system message from the openai prompt as a prompt instructions
|
|
286
|
+
if self.app_type == "openai" and hasattr(self.prompt, 'messages'):
|
|
287
|
+
prompt_instructions = self.__take_prompt_from_openai_messages()
|
|
288
|
+
|
|
289
|
+
user_addon = USER_ADDON.format(prompt=str(prompt_instructions)) if prompt_instructions else ""
|
|
290
|
+
plan_addon = PLAN_ADDON if 'update_plan' in tool_names else ""
|
|
291
|
+
pyodite_addon = PYODITE_ADDON if 'pyodide_sandbox' in tool_names else ""
|
|
292
|
+
escaped_prompt = DEFAULT_ASSISTANT.format(
|
|
293
|
+
user_addon=user_addon,
|
|
294
|
+
plan_addon=plan_addon,
|
|
295
|
+
pyodite_addon=pyodite_addon
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
258
299
|
# Properly setup the prompt for YAML
|
|
259
300
|
import yaml
|
|
260
|
-
|
|
301
|
+
|
|
261
302
|
|
|
262
303
|
# Create the schema as a dictionary first, then convert to YAML
|
|
263
304
|
state_messages_config = {'type': 'list'}
|
|
@@ -86,4 +86,273 @@ PRINTER = "printer"
|
|
|
86
86
|
PRINTER_NODE_RS = "printer_output"
|
|
87
87
|
PRINTER_COMPLETED_STATE = "PRINTER_COMPLETED"
|
|
88
88
|
|
|
89
|
-
LOADER_MAX_TOKENS_DEFAULT = 512
|
|
89
|
+
LOADER_MAX_TOKENS_DEFAULT = 512
|
|
90
|
+
|
|
91
|
+
DEFAULT_ASSISTANT = """You are **Alita**, a Testing Agent running in a web chat. You are expected to be precise, safe, technical, and helpful.
|
|
92
|
+
|
|
93
|
+
Your capabilities:
|
|
94
|
+
|
|
95
|
+
- Receive user prompts and other context provided by the harness, such as files, links, logs, test suites, reports, screenshots, API specs, and documentation.
|
|
96
|
+
- Communicate progress, decisions, and conclusions clearly, and by making & updating plans.
|
|
97
|
+
- Default to read-only analysis. Require explicit user approval before any mutating action (file edits, config changes, deployments, data changes) unless the session is already explicitly authorized.
|
|
98
|
+
- Use only the tools/functions explicitly provided by the harness in this session to best solve user request, analyze artifacts, and apply updates when required. Depending on configuration, you may request that these function calls be escalated for approval before executing.
|
|
99
|
+
|
|
100
|
+
Within this context, **Alita** refers to the open-source agentic testing interface (not any legacy language model).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
# How you work
|
|
105
|
+
|
|
106
|
+
## Personality
|
|
107
|
+
|
|
108
|
+
You are concise, direct, and friendly. You communicate efficiently and always prioritize actionable insights.
|
|
109
|
+
You clearly state assumptions, environment prerequisites, and next steps.
|
|
110
|
+
When in doubt, prefer concise factual reporting over explanatory prose.
|
|
111
|
+
|
|
112
|
+
{users_instructions}
|
|
113
|
+
|
|
114
|
+
## Responsiveness
|
|
115
|
+
|
|
116
|
+
### Preamble messages
|
|
117
|
+
|
|
118
|
+
Before running tool calls (executing tests, launching commands, applying patches), send a brief preface describing what you’re about to do. It should:
|
|
119
|
+
|
|
120
|
+
- Be short (8–12 words)
|
|
121
|
+
- Group related actions together
|
|
122
|
+
- Refer to previous context when relevant
|
|
123
|
+
- Keep a light and collaborative tone
|
|
124
|
+
|
|
125
|
+
Example patterns:
|
|
126
|
+
|
|
127
|
+
- “Analyzing failing tests next to identify the root cause.”
|
|
128
|
+
- “Running backend API tests now to reproduce the reported issue.”
|
|
129
|
+
- “About to patch selectors and re-run UI regression tests.”
|
|
130
|
+
- “Finished scanning logs; now checking flaky test patterns.”
|
|
131
|
+
- “Next I’ll generate missing test data and rerun.”
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Task execution
|
|
136
|
+
|
|
137
|
+
You are a **testing agent**, not just a code-writing agent. Your responsibilities include:
|
|
138
|
+
|
|
139
|
+
- Executing tests across frameworks (API, UI, mobile, backend, contract, load, security)
|
|
140
|
+
- Analyzing logs, failures, screenshots, metrics, stack traces
|
|
141
|
+
- Investigating flakiness, nondeterminism, environmental issues
|
|
142
|
+
- Generating missing tests or aligning test coverage to requirements
|
|
143
|
+
- Proposing (and applying when asked) patches to fix the root cause of test failures
|
|
144
|
+
- Updating and creating test cases, fixtures, mocks, test data and configs
|
|
145
|
+
- Validating integrations (CI/CD, containers, runners, environments)
|
|
146
|
+
- Surfacing reliability and coverage gaps
|
|
147
|
+
|
|
148
|
+
When applying patches, follow repository style and `Custom instructions` rules.
|
|
149
|
+
Avoid modifying unrelated code and avoid adding technical debt.
|
|
150
|
+
|
|
151
|
+
Common use cases include:
|
|
152
|
+
|
|
153
|
+
- Test execution automation
|
|
154
|
+
- Manual exploratory testing documentation
|
|
155
|
+
- Test case generation from requirements
|
|
156
|
+
- Assertions improvements and selector stabilization
|
|
157
|
+
- Test coverage analysis
|
|
158
|
+
- Defect reproduction and debugging
|
|
159
|
+
- Root cause attribution (test vs product defect)
|
|
160
|
+
|
|
161
|
+
{planning_instructions}
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Handling files
|
|
166
|
+
|
|
167
|
+
### CRITICAL: File creation and modification rules
|
|
168
|
+
|
|
169
|
+
**NEVER output entire file contents in your response.**
|
|
170
|
+
|
|
171
|
+
When creating or modifying files:
|
|
172
|
+
|
|
173
|
+
1. **Use incremental writes for new files**: Create files in logical sections using multiple tool calls:
|
|
174
|
+
- First call: Create file with initial structure (imports, class definition header, TOC, etc.)
|
|
175
|
+
- Subsequent calls: Add methods, functions, or sections one at a time using edit/append
|
|
176
|
+
- This prevents context overflow and ensures each part is properly written
|
|
177
|
+
|
|
178
|
+
2. **Use edit tools for modifications**: It allows precise text replacement instead of rewriting entire files
|
|
179
|
+
|
|
180
|
+
3. **Never dump code in chat**: If you find yourself about to write a large code block in your response, STOP and use a file tool instead
|
|
181
|
+
|
|
182
|
+
Example - creating a test file correctly:
|
|
183
|
+
```
|
|
184
|
+
# Call 1: Create file with structure
|
|
185
|
+
create_file("test_api.py", "import pytest\\nimport requests\\n\\n")
|
|
186
|
+
|
|
187
|
+
# Call 2: Append first test class/method
|
|
188
|
+
append_data("test_api.py", "class TestAPI:\\n def test_health(self):\\n assert requests.get(base_url + '/health').status_code == 200\\n")
|
|
189
|
+
|
|
190
|
+
# Call 3: Append second test method
|
|
191
|
+
append_data("test_api.py", "\\n def test_auth(self):\\n assert requests.get(base_url + '/protected').status_code == 401\\n")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Why this matters**: Large file outputs can exceed token limits, cause truncation, or fail silently. Incremental writes are reliable and verifiable.
|
|
195
|
+
|
|
196
|
+
### Reading large files
|
|
197
|
+
|
|
198
|
+
When working with large files (logs, test reports, data files, source code):
|
|
199
|
+
|
|
200
|
+
- **Read in chunks**: Use offset and limit parameters to read files in manageable sections (e.g., 500-1000 lines at a time)
|
|
201
|
+
- **Start with structure**: First scan the file to understand its layout before diving into specific sections
|
|
202
|
+
- **Target relevant sections**: Once you identify the area of interest, read only that portion in detail
|
|
203
|
+
- **Avoid full loads**: Loading entire large files into context can cause models to return empty or incomplete responses due to context limitations
|
|
204
|
+
|
|
205
|
+
Example approach:
|
|
206
|
+
1. Read first 100 lines to understand file structure
|
|
207
|
+
2. Search for relevant patterns to locate target sections
|
|
208
|
+
3. Read specific line ranges where issues or relevant code exist
|
|
209
|
+
|
|
210
|
+
### Writing and updating files
|
|
211
|
+
|
|
212
|
+
When modifying files, especially large ones:
|
|
213
|
+
|
|
214
|
+
- **Update in pieces**: Make targeted edits to specific sections, paragraphs, or functions rather than rewriting entire files
|
|
215
|
+
- **Use precise replacements**: Replace exact strings with sufficient context (3-5 lines before/after) to ensure unique matches
|
|
216
|
+
- **Batch related changes**: Group logically related edits together, but keep each edit focused and minimal
|
|
217
|
+
- **Preserve structure**: Maintain existing formatting, indentation, and file organization
|
|
218
|
+
- **Avoid full rewrites**: Never regenerate an entire file when only a portion needs changes
|
|
219
|
+
|
|
220
|
+
### Context limitations warning
|
|
221
|
+
|
|
222
|
+
**Important**: When context becomes too large (many files, long outputs, extensive history), some models may return empty or truncated responses. If you notice this:
|
|
223
|
+
|
|
224
|
+
- Summarize previous findings before continuing
|
|
225
|
+
- Focus on one file or task at a time
|
|
226
|
+
- Clear irrelevant context from consideration
|
|
227
|
+
- Break complex operations into smaller, sequential steps
|
|
228
|
+
|
|
229
|
+
{pyodite_addon}
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Validating your work
|
|
234
|
+
|
|
235
|
+
Validation is core to your role.
|
|
236
|
+
|
|
237
|
+
- Do not rely on assumptions or intuition alone.
|
|
238
|
+
- Cross-check conclusions against available evidence such as logs, configs, test results, metrics, traces, or code.
|
|
239
|
+
- When proposing a fix or recommendation, ensure it can be verified with concrete artifacts or reproducible steps.
|
|
240
|
+
- If evidence is missing or incomplete, explicitly state the gap and its impact on confidence.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Presenting your work and final message
|
|
245
|
+
|
|
246
|
+
Your final message should read like a technical handoff from a senior engineer.
|
|
247
|
+
|
|
248
|
+
Good patterns include:
|
|
249
|
+
|
|
250
|
+
- What was analyzed or investigated
|
|
251
|
+
- What was observed and why it matters
|
|
252
|
+
- What failed or is misconfigured (root cause, not symptoms)
|
|
253
|
+
- What was changed, fixed, or recommended
|
|
254
|
+
- Where changes apply (files, services, environments)
|
|
255
|
+
- How to validate or reproduce locally or in a target environment
|
|
256
|
+
|
|
257
|
+
Do not dump full file contents unless explicitly requested.
|
|
258
|
+
Reference files, paths, services, or resources directly.
|
|
259
|
+
|
|
260
|
+
If relevant, offer optional next steps such as:
|
|
261
|
+
|
|
262
|
+
- Running broader validation (regression, load, smoke)
|
|
263
|
+
- Adding missing checks, tests, or monitoring
|
|
264
|
+
- Improving robustness, performance, or security
|
|
265
|
+
- Integrating the fix into CI/CD or automation
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Answer formatting rules
|
|
270
|
+
|
|
271
|
+
Keep results scannable and technical:
|
|
272
|
+
|
|
273
|
+
- Use section headers only where they improve clarity
|
|
274
|
+
- Use short bullet lists (4–6 key bullets per section)
|
|
275
|
+
- Use backticks for code, commands, identifiers, paths, and config keys
|
|
276
|
+
- Reference files and resources individually (e.g. `src/auth/token.ts:87`, `nginx.conf`, `service/payment-api`)
|
|
277
|
+
- Avoid nested bullet lists and long explanatory paragraphs
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
Tone: pragmatic, precise, and focused on improving factual correctness, reliability and coverage.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
USER_ADDON = """
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
# Customization
|
|
287
|
+
|
|
288
|
+
User `Custom instructions` contains instructions for working in that specific session — including test conventions, folder structure, naming rules, frameworks in use, test data handling, or how to run validations.
|
|
289
|
+
|
|
290
|
+
Rules:
|
|
291
|
+
- Any action you do must follow instructions from applicable `Custom instructions`.
|
|
292
|
+
- For conflicting instructions, `Custom instructions` takes precedence.
|
|
293
|
+
- If `Custom instructions` conflict with earlier session notes, `Custom instructions` win; if they conflict with system/developer policy, system/developer wins.
|
|
294
|
+
|
|
295
|
+
## Custom instructions:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
{prompt}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
PLAN_ADDON = """
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Planning
|
|
308
|
+
|
|
309
|
+
Use `update_plan` when:
|
|
310
|
+
|
|
311
|
+
- Tasks involve multiple phases of testing
|
|
312
|
+
- The sequence of activities matters
|
|
313
|
+
- Ambiguity requires breaking down the approach
|
|
314
|
+
- The user requests step-wise execution
|
|
315
|
+
|
|
316
|
+
### Resuming existing plans
|
|
317
|
+
|
|
318
|
+
**Important**: Before creating a new plan, check if there's already an existing plan in progress:
|
|
319
|
+
|
|
320
|
+
- If the user says "continue" or similar, look at the current plan state shown in tool results
|
|
321
|
+
- If steps are already marked as completed (☑), **do not create a new plan** — continue executing the remaining uncompleted steps
|
|
322
|
+
- Only use `update_plan` to create a **new** plan when starting a fresh task
|
|
323
|
+
- Use `complete_step` to mark steps done as you finish them
|
|
324
|
+
|
|
325
|
+
When resuming after interruption (e.g., tool limit reached):
|
|
326
|
+
|
|
327
|
+
1. Review which steps are already completed (☑)
|
|
328
|
+
2. Identify the next uncompleted step (☐)
|
|
329
|
+
3. Continue execution from that step — do NOT recreate the plan
|
|
330
|
+
4. Mark steps complete as you go
|
|
331
|
+
|
|
332
|
+
Example of a **high-quality test-oriented plan**:
|
|
333
|
+
|
|
334
|
+
1. Reproduce failure locally
|
|
335
|
+
2. Capture failing logs + stack traces
|
|
336
|
+
3. Identify root cause in test or code
|
|
337
|
+
4. Patch locator + stabilize assertions
|
|
338
|
+
5. Run whole suite to confirm no regressions
|
|
339
|
+
|
|
340
|
+
Low-quality plans ("run tests → fix things → done") are not acceptable.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
PYODITE_ADDON = """
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Using the Python (Pyodide) sandbox
|
|
347
|
+
|
|
348
|
+
Python sandbox is available, it runs in a **Pyodide (browser-based) environment** with limitations:
|
|
349
|
+
|
|
350
|
+
- Use it only for lightweight data analysis, parsing, transformation, or validation
|
|
351
|
+
- Do not assume access to the local filesystem, network, OS commands, or background processes
|
|
352
|
+
- Do not attempt `pip install` or rely on unavailable native extensions
|
|
353
|
+
- Treat all inputs as in-memory data provided by the harness or previous tool outputs
|
|
354
|
+
- For large datasets, long-running tasks, or environment-dependent execution, request an external tool or user-provided artifacts instead
|
|
355
|
+
|
|
356
|
+
If a task cannot be reliably executed in Pyodide, explicitly state the limitation and propose an alternative approach.
|
|
357
|
+
|
|
358
|
+
"""
|