alita-sdk 0.3.365__py3-none-any.whl → 0.3.462__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 (118) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent_executor.py +144 -0
  4. alita_sdk/cli/agent_loader.py +197 -0
  5. alita_sdk/cli/agent_ui.py +166 -0
  6. alita_sdk/cli/agents.py +1069 -0
  7. alita_sdk/cli/callbacks.py +576 -0
  8. alita_sdk/cli/cli.py +159 -0
  9. alita_sdk/cli/config.py +153 -0
  10. alita_sdk/cli/formatting.py +182 -0
  11. alita_sdk/cli/mcp_loader.py +315 -0
  12. alita_sdk/cli/toolkit.py +330 -0
  13. alita_sdk/cli/toolkit_loader.py +55 -0
  14. alita_sdk/cli/tools/__init__.py +9 -0
  15. alita_sdk/cli/tools/filesystem.py +905 -0
  16. alita_sdk/configurations/bitbucket.py +95 -0
  17. alita_sdk/configurations/confluence.py +96 -1
  18. alita_sdk/configurations/gitlab.py +79 -0
  19. alita_sdk/configurations/jira.py +103 -0
  20. alita_sdk/configurations/testrail.py +88 -0
  21. alita_sdk/configurations/xray.py +93 -0
  22. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  23. alita_sdk/configurations/zephyr_essential.py +75 -0
  24. alita_sdk/runtime/clients/artifact.py +1 -1
  25. alita_sdk/runtime/clients/client.py +47 -10
  26. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  27. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  28. alita_sdk/runtime/clients/sandbox_client.py +373 -0
  29. alita_sdk/runtime/langchain/assistant.py +70 -41
  30. alita_sdk/runtime/langchain/constants.py +6 -1
  31. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  32. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
  33. alita_sdk/runtime/langchain/document_loaders/constants.py +73 -100
  34. alita_sdk/runtime/langchain/langraph_agent.py +164 -38
  35. alita_sdk/runtime/langchain/utils.py +43 -7
  36. alita_sdk/runtime/models/mcp_models.py +61 -0
  37. alita_sdk/runtime/toolkits/__init__.py +24 -0
  38. alita_sdk/runtime/toolkits/application.py +8 -1
  39. alita_sdk/runtime/toolkits/artifact.py +5 -6
  40. alita_sdk/runtime/toolkits/mcp.py +895 -0
  41. alita_sdk/runtime/toolkits/tools.py +140 -50
  42. alita_sdk/runtime/tools/__init__.py +7 -2
  43. alita_sdk/runtime/tools/application.py +7 -0
  44. alita_sdk/runtime/tools/function.py +94 -5
  45. alita_sdk/runtime/tools/graph.py +10 -4
  46. alita_sdk/runtime/tools/image_generation.py +104 -8
  47. alita_sdk/runtime/tools/llm.py +204 -114
  48. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  49. alita_sdk/runtime/tools/mcp_remote_tool.py +166 -0
  50. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  51. alita_sdk/runtime/tools/sandbox.py +180 -79
  52. alita_sdk/runtime/tools/vectorstore.py +22 -21
  53. alita_sdk/runtime/tools/vectorstore_base.py +79 -26
  54. alita_sdk/runtime/utils/mcp_oauth.py +164 -0
  55. alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
  56. alita_sdk/runtime/utils/streamlit.py +34 -3
  57. alita_sdk/runtime/utils/toolkit_utils.py +14 -4
  58. alita_sdk/runtime/utils/utils.py +1 -0
  59. alita_sdk/tools/__init__.py +48 -31
  60. alita_sdk/tools/ado/repos/__init__.py +1 -0
  61. alita_sdk/tools/ado/test_plan/__init__.py +1 -1
  62. alita_sdk/tools/ado/wiki/__init__.py +1 -5
  63. alita_sdk/tools/ado/work_item/__init__.py +1 -5
  64. alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
  65. alita_sdk/tools/base_indexer_toolkit.py +194 -112
  66. alita_sdk/tools/bitbucket/__init__.py +1 -0
  67. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  68. alita_sdk/tools/code/sonar/__init__.py +1 -1
  69. alita_sdk/tools/code_indexer_toolkit.py +15 -5
  70. alita_sdk/tools/confluence/__init__.py +2 -2
  71. alita_sdk/tools/confluence/api_wrapper.py +110 -63
  72. alita_sdk/tools/confluence/loader.py +10 -0
  73. alita_sdk/tools/elitea_base.py +22 -22
  74. alita_sdk/tools/github/__init__.py +2 -2
  75. alita_sdk/tools/gitlab/__init__.py +2 -1
  76. alita_sdk/tools/gitlab/api_wrapper.py +11 -7
  77. alita_sdk/tools/gitlab_org/__init__.py +1 -2
  78. alita_sdk/tools/google_places/__init__.py +2 -1
  79. alita_sdk/tools/jira/__init__.py +1 -0
  80. alita_sdk/tools/jira/api_wrapper.py +1 -1
  81. alita_sdk/tools/memory/__init__.py +1 -1
  82. alita_sdk/tools/non_code_indexer_toolkit.py +2 -2
  83. alita_sdk/tools/openapi/__init__.py +10 -1
  84. alita_sdk/tools/pandas/__init__.py +1 -1
  85. alita_sdk/tools/postman/__init__.py +2 -1
  86. alita_sdk/tools/postman/api_wrapper.py +18 -8
  87. alita_sdk/tools/postman/postman_analysis.py +8 -1
  88. alita_sdk/tools/pptx/__init__.py +2 -2
  89. alita_sdk/tools/qtest/__init__.py +3 -3
  90. alita_sdk/tools/qtest/api_wrapper.py +1708 -76
  91. alita_sdk/tools/rally/__init__.py +1 -2
  92. alita_sdk/tools/report_portal/__init__.py +1 -0
  93. alita_sdk/tools/salesforce/__init__.py +1 -0
  94. alita_sdk/tools/servicenow/__init__.py +2 -3
  95. alita_sdk/tools/sharepoint/__init__.py +1 -0
  96. alita_sdk/tools/sharepoint/api_wrapper.py +125 -34
  97. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  98. alita_sdk/tools/sharepoint/utils.py +8 -2
  99. alita_sdk/tools/slack/__init__.py +1 -0
  100. alita_sdk/tools/sql/__init__.py +2 -1
  101. alita_sdk/tools/sql/api_wrapper.py +71 -23
  102. alita_sdk/tools/testio/__init__.py +1 -0
  103. alita_sdk/tools/testrail/__init__.py +1 -3
  104. alita_sdk/tools/utils/__init__.py +17 -0
  105. alita_sdk/tools/utils/content_parser.py +35 -24
  106. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +67 -21
  107. alita_sdk/tools/xray/__init__.py +2 -1
  108. alita_sdk/tools/zephyr/__init__.py +2 -1
  109. alita_sdk/tools/zephyr_enterprise/__init__.py +1 -0
  110. alita_sdk/tools/zephyr_essential/__init__.py +1 -0
  111. alita_sdk/tools/zephyr_scale/__init__.py +1 -0
  112. alita_sdk/tools/zephyr_squad/__init__.py +1 -0
  113. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/METADATA +8 -2
  114. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/RECORD +118 -93
  115. alita_sdk-0.3.462.dist-info/entry_points.txt +2 -0
  116. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/WHEEL +0 -0
  117. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/licenses/LICENSE +0 -0
  118. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  import logging
2
- from typing import Optional, Any
2
+ from typing import Optional
3
3
 
4
- from pydantic import BaseModel, create_model, model_validator, Field, SecretStr
5
- from pydantic.fields import PrivateAttr
4
+ from langchain_core.tools import ToolException
5
+ from pydantic import create_model, SecretStr, model_validator
6
+ from pydantic.fields import PrivateAttr, Field
6
7
  from sqlalchemy import create_engine, text, inspect, Engine
7
8
  from sqlalchemy.orm import sessionmaker
8
9
 
@@ -27,7 +28,7 @@ class SQLApiWrapper(BaseToolApiWrapper):
27
28
  username: str
28
29
  password: SecretStr
29
30
  database_name: str
30
- _client: Optional[Engine] = PrivateAttr()
31
+ _client: Optional[Engine] = PrivateAttr(default=None)
31
32
 
32
33
  @model_validator(mode='before')
33
34
  @classmethod
@@ -35,27 +36,73 @@ class SQLApiWrapper(BaseToolApiWrapper):
35
36
  for field in SQLConfig.model_fields:
36
37
  if field not in values or not values[field]:
37
38
  raise ValueError(f"{field} is a required field and must be provided.")
38
-
39
- dialect = values['dialect']
40
- host = values['host']
41
- username = values['username']
42
- password = values['password']
43
- database_name = values['database_name']
44
- port = values['port']
45
-
46
- if dialect == SQLDialect.POSTGRES:
47
- connection_string = f'postgresql+psycopg2://{username}:{password}@{host}:{port}/{database_name}'
48
- elif dialect == SQLDialect.MYSQL:
49
- connection_string = f'mysql+pymysql://{username}:{password}@{host}:{port}/{database_name}'
50
- else:
51
- raise ValueError(f"Unsupported database type. Supported types are: {[e.value for e in SQLDialect]}")
52
-
53
- cls._client = create_engine(connection_string)
54
39
  return values
55
40
 
41
+ def _mask_password_in_error(self, error_message: str) -> str:
42
+ """Mask password in error messages, showing only last 4 characters."""
43
+ password_str = self.password.get_secret_value()
44
+ if len(password_str) <= 4:
45
+ masked_password = "****"
46
+ else:
47
+ masked_password = "****" + password_str[-4:]
48
+
49
+ # Replace all occurrences of the password, and any substring of the password that may appear in the error message
50
+ for part in [password_str, password_str.replace('@', ''), password_str.split('@')[-1]]:
51
+ if part and part in error_message:
52
+ error_message = error_message.replace(part, masked_password)
53
+ return error_message
54
+
55
+ @property
56
+ def client(self) -> Engine:
57
+ """Lazy property to create and return database engine with error handling."""
58
+ if self._client is None:
59
+ try:
60
+ dialect = self.dialect
61
+ host = self.host
62
+ username = self.username
63
+ password = self.password.get_secret_value()
64
+ database_name = self.database_name
65
+ port = self.port
66
+
67
+ if dialect == SQLDialect.POSTGRES:
68
+ connection_string = f'postgresql+psycopg2://{username}:{password}@{host}:{port}/{database_name}'
69
+ elif dialect == SQLDialect.MYSQL:
70
+ connection_string = f'mysql+pymysql://{username}:{password}@{host}:{port}/{database_name}'
71
+ else:
72
+ raise ValueError(f"Unsupported database type. Supported types are: {[e.value for e in SQLDialect]}")
73
+
74
+ self._client = create_engine(connection_string)
75
+
76
+ # Test the connection
77
+ with self._client.connect() as conn:
78
+ conn.execute(text("SELECT 1"))
79
+
80
+ except Exception as e:
81
+ error_message = str(e)
82
+ masked_error = self._mask_password_in_error(error_message)
83
+ logger.error(f"Database connection failed: {masked_error}")
84
+ raise ValueError(f"Database connection failed: {masked_error}")
85
+
86
+ return self._client
87
+
88
+ def _handle_database_errors(func):
89
+ """Decorator to catch exceptions and mask passwords in error messages."""
90
+
91
+ def wrapper(self, *args, **kwargs):
92
+ try:
93
+ return func(self, *args, **kwargs)
94
+ except Exception as e:
95
+ error_message = str(e)
96
+ masked_error = self._mask_password_in_error(error_message)
97
+ logger.error(f"Database operation failed in {func.__name__}: {masked_error}")
98
+ raise ToolException(masked_error)
99
+
100
+ return wrapper
101
+
102
+ @_handle_database_errors
56
103
  def execute_sql(self, sql_query: str):
57
104
  """Executes the provided SQL query on the configured database."""
58
- engine = self._client
105
+ engine = self.client
59
106
  maker_session = sessionmaker(bind=engine)
60
107
  session = maker_session()
61
108
  try:
@@ -76,9 +123,10 @@ class SQLApiWrapper(BaseToolApiWrapper):
76
123
  finally:
77
124
  session.close()
78
125
 
126
+ @_handle_database_errors
79
127
  def list_tables_and_columns(self):
80
128
  """Lists all tables and their columns in the configured database."""
81
- inspector = inspect(self._client)
129
+ inspector = inspect(self.client)
82
130
  data = {}
83
131
  tables = inspector.get_table_names()
84
132
  for table in tables:
@@ -109,4 +157,4 @@ class SQLApiWrapper(BaseToolApiWrapper):
109
157
  "description": self.list_tables_and_columns.__doc__,
110
158
  "args_schema": SQLNoInput,
111
159
  }
112
- ]
160
+ ]
@@ -33,6 +33,7 @@ class TestIOToolkit(BaseToolkit):
33
33
  testio_configuration=(TestIOConfiguration, Field(description="TestIO Configuration", json_schema_extra={'configuration_types': ['testio']})),
34
34
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
35
35
  __config__=ConfigDict(json_schema_extra={'metadata': {"label": "TestIO", "icon_url": "testio-icon.svg",
36
+ "max_length": TOOLKIT_MAX_LENGTH,
36
37
  "categories": ["testing"],
37
38
  "extra_categories": ["test automation", "test case management", "test planning"]}})
38
39
  )
@@ -39,9 +39,6 @@ class TestrailToolkit(BaseToolkit):
39
39
  TestrailToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
40
40
  m = create_model(
41
41
  name,
42
- name=(str, Field(description="Toolkit name", json_schema_extra={
43
- 'toolkit_name': True,
44
- "max_length": TestrailToolkit.toolkit_max_length})),
45
42
  testrail_configuration=(Optional[TestRailConfiguration], Field(description="TestRail Configuration", json_schema_extra={'configuration_types': ['testrail']})),
46
43
  pgvector_configuration=(Optional[PgVectorConfiguration], Field(default = None,
47
44
  description="PgVector Configuration", json_schema_extra={'configuration_types': ['pgvector']})),
@@ -50,6 +47,7 @@ class TestrailToolkit(BaseToolkit):
50
47
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
51
48
  __config__=ConfigDict(json_schema_extra={'metadata':
52
49
  {"label": "Testrail", "icon_url": "testrail-icon.svg",
50
+ "max_length": TestrailToolkit.toolkit_max_length,
53
51
  "categories": ["test management"],
54
52
  "extra_categories": ["quality assurance", "test case management", "test planning"]
55
53
  }})
@@ -97,3 +97,20 @@ def check_connection_response(check_fun):
97
97
  else:
98
98
  return f"Service Unreachable: return code {response.status_code}"
99
99
  return _wrapper
100
+
101
+
102
+ def make_json_serializable(obj):
103
+ if isinstance(obj, BaseModel):
104
+ return obj.model_dump()
105
+ if isinstance(obj, dict):
106
+ return {k: make_json_serializable(v) for k, v in obj.items()}
107
+ if isinstance(obj, list):
108
+ return [make_json_serializable(i) for i in obj]
109
+ if isinstance(obj, bool):
110
+ return bool(obj)
111
+ if isinstance(obj, (str, int, float)) or obj is None:
112
+ return obj
113
+ # Fallback: handle objects that look like booleans but were not caught above
114
+ if str(obj) in ("True", "False"):
115
+ return str(obj) == "True"
116
+ return str(obj)
@@ -92,21 +92,24 @@ def parse_file_content(file_name=None, file_content=None, is_capture_image: bool
92
92
  return ToolException(
93
93
  "Not supported type of files entered. Supported types are TXT, DOCX, PDF, PPTX, XLSX and XLS only.")
94
94
 
95
- if hasattr(loader, 'get_content'):
96
- return loader.get_content()
97
- else:
98
- extension = Path(file_path if file_path else file_name).suffix
99
- loader_kwargs = get_loader_kwargs(loaders_map.get(extension), file_name, file_content, is_capture_image, page_number, sheet_name, llm, file_path, excel_by_sheets)
100
- if file_content:
101
- return load_content_from_bytes(file_content=file_content,
102
- extension=extension,
103
- loader_extra_config=loader_kwargs,
104
- llm=llm)
95
+ try:
96
+ if hasattr(loader, 'get_content'):
97
+ return loader.get_content()
105
98
  else:
106
- return load_content(file_path=file_path,
107
- extension=extension,
108
- loader_extra_config=loader_kwargs,
109
- llm=llm)
99
+ extension = Path(file_path if file_path else file_name).suffix
100
+ loader_kwargs = get_loader_kwargs(loaders_map.get(extension), file_name, file_content, is_capture_image, page_number, sheet_name, llm, file_path, excel_by_sheets)
101
+ if file_content:
102
+ return load_content_from_bytes(file_content=file_content,
103
+ extension=extension,
104
+ loader_extra_config=loader_kwargs,
105
+ llm=llm)
106
+ else:
107
+ return load_content(file_path=file_path,
108
+ extension=extension,
109
+ loader_extra_config=loader_kwargs,
110
+ llm=llm)
111
+ except Exception as e:
112
+ return ToolException(f"Error reading file ({file_name or file_path}) content. Make sure these types are supported: {str(e)}")
110
113
 
111
114
  def load_file_docs(file_name=None, file_content=None, is_capture_image: bool = False, page_number: int = None,
112
115
  sheet_name: str = None, llm=None, file_path: str = None, excel_by_sheets: bool = False) -> List[Document] | ToolException:
@@ -153,7 +156,7 @@ def prepare_loader(file_name=None, file_content=None, is_capture_image: bool = F
153
156
 
154
157
  loader_object = loaders_map.get(extension)
155
158
  if not loader_object:
156
- return None
159
+ loader_object = loaders_map.get('.txt') # Default to text loader if no specific loader found
157
160
  loader_kwargs = get_loader_kwargs(loader_object, file_name, file_content, is_capture_image, page_number, sheet_name, llm, file_path, excel_by_sheets, prompt)
158
161
  loader = loader_object['class'](**loader_kwargs)
159
162
  return loader
@@ -222,10 +225,18 @@ def process_document_by_type(content, extension_source: str, document: Document
222
225
  metadata={**document.metadata, 'chunk_id': 1}
223
226
  )
224
227
  return
228
+ #
229
+ chunks_counter = 0
225
230
  for chunk in chunks:
231
+ chunks_counter += 1
232
+ metadata = {**document.metadata, **chunk.metadata}
233
+ #
234
+ # ensure each chunk has a unique chunk_id
235
+ metadata['chunk_id'] = chunks_counter
236
+ #
226
237
  yield Document(
227
238
  page_content=sanitize_for_postgres(chunk.page_content),
228
- metadata={**document.metadata, **chunk.metadata}
239
+ metadata=metadata
229
240
  )
230
241
 
231
242
 
@@ -262,18 +273,18 @@ def process_content_by_type(content, filename: str, llm=None, chunking_config=No
262
273
  loader_kwargs = loader_config['kwargs']
263
274
  # Determine which loader configuration keys are allowed to be overridden by user input.
264
275
  # If 'allowed_to_override' is specified in the loader configuration, use it; otherwise, allow all keys in loader_kwargs.
265
- allowed_to_override = loader_config.get('allowed_to_override', list(loader_kwargs.keys()))
276
+ allowed_to_override = loader_config.get('allowed_to_override', loader_kwargs)
266
277
  # If a chunking_config is provided and contains custom configuration for the current file extension,
267
- # update loader_kwargs with user-supplied values, but only for keys explicitly permitted in allowed_to_override.
278
+ # update loader_kwargs with user-supplied values, but only for keys explicitly permitted in allowed_to_override and if value differs from default.
268
279
  # This ensures that only safe and intended parameters can be customized, preventing accidental or unauthorized changes
269
280
  # to critical loader settings.
270
281
  if chunking_config and (users_config_for_extension := chunking_config.get(extension, {})):
271
- for key in set(users_config_for_extension.keys()) & set(allowed_to_override):
272
- loader_kwargs[key] = users_config_for_extension[key]
273
- if LoaderProperties.LLM.value in loader_kwargs:
274
- loader_kwargs[LoaderProperties.LLM.value] = llm
275
- if LoaderProperties.PROMPT_DEFAULT.value in loader_kwargs:
276
- loader_kwargs.pop(LoaderProperties.PROMPT_DEFAULT.value)
282
+ for key in set(users_config_for_extension.keys()) & set(allowed_to_override.keys()):
283
+ if users_config_for_extension[key] != allowed_to_override[key]:
284
+ loader_kwargs[key] = users_config_for_extension[key]
285
+ if LoaderProperties.LLM.value in loader_kwargs and loader_kwargs.pop(LoaderProperties.LLM.value):
286
+ loader_kwargs['llm'] = llm
287
+ if LoaderProperties.PROMPT_DEFAULT.value in loader_kwargs and loader_kwargs.pop(LoaderProperties.PROMPT_DEFAULT.value):
277
288
  loader_kwargs[LoaderProperties.PROMPT.value] = image_processing_prompt
278
289
  loader = loader_cls(file_path=temp_file_path, **loader_kwargs)
279
290
  yield from loader.load()
@@ -2,6 +2,8 @@ from abc import ABC, abstractmethod
2
2
  from typing import Any, Dict, Optional, List
3
3
  from logging import getLogger
4
4
 
5
+ from ...runtime.utils.utils import IndexerKeywords
6
+
5
7
  logger = getLogger(__name__)
6
8
 
7
9
 
@@ -24,12 +26,12 @@ class VectorStoreAdapter(ABC):
24
26
  pass
25
27
 
26
28
  @abstractmethod
27
- def get_indexed_ids(self, vectorstore_wrapper, collection_suffix: Optional[str] = '') -> List[str]:
29
+ def get_indexed_ids(self, vectorstore_wrapper, index_name: Optional[str] = '') -> List[str]:
28
30
  """Get all indexed document IDs from vectorstore"""
29
31
  pass
30
32
 
31
33
  @abstractmethod
32
- def clean_collection(self, vectorstore_wrapper, collection_suffix: str = ''):
34
+ def clean_collection(self, vectorstore_wrapper, index_name: str = ''):
33
35
  """Clean the vectorstore collection by deleting all indexed data."""
34
36
  pass
35
37
 
@@ -39,7 +41,7 @@ class VectorStoreAdapter(ABC):
39
41
  pass
40
42
 
41
43
  @abstractmethod
42
- def get_code_indexed_data(self, vectorstore_wrapper, collection_suffix) -> Dict[str, Dict[str, Any]]:
44
+ def get_code_indexed_data(self, vectorstore_wrapper, index_name) -> Dict[str, Dict[str, Any]]:
43
45
  """Get all indexed data from vectorstore for code content"""
44
46
  pass
45
47
 
@@ -48,6 +50,11 @@ class VectorStoreAdapter(ABC):
48
50
  """Add a new collection name to the metadata"""
49
51
  pass
50
52
 
53
+ @abstractmethod
54
+ def get_index_meta(self, vectorstore_wrapper, index_name: str) -> List[Dict[str, Any]]:
55
+ """Get all index_meta entries from the vector store."""
56
+ pass
57
+
51
58
 
52
59
  class PGVectorAdapter(VectorStoreAdapter):
53
60
  """Adapter for PGVector database operations."""
@@ -99,20 +106,25 @@ class PGVectorAdapter(VectorStoreAdapter):
99
106
  session.commit()
100
107
  logger.info(f"Schema '{schema_name}' has been dropped.")
101
108
 
102
- def get_indexed_ids(self, vectorstore_wrapper, collection_suffix: Optional[str] = '') -> List[str]:
109
+ def get_indexed_ids(self, vectorstore_wrapper, index_name: Optional[str] = '') -> List[str]:
103
110
  """Get all indexed document IDs from PGVector"""
104
111
  from sqlalchemy.orm import Session
105
- from sqlalchemy import func
112
+ from sqlalchemy import func, or_
106
113
 
107
114
  store = vectorstore_wrapper.vectorstore
108
115
  try:
109
116
  with Session(store.session_maker.bind) as session:
110
117
  # Start building the query
111
118
  query = session.query(store.EmbeddingStore.id)
112
- # Apply filter only if collection_suffix is provided
113
- if collection_suffix:
119
+ # Apply filter only if index_name is provided
120
+ if index_name:
114
121
  query = query.filter(
115
- func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == collection_suffix
122
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name,
123
+ or_(
124
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'type').is_(None),
125
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata,
126
+ 'type') != IndexerKeywords.INDEX_META_TYPE.value
127
+ )
116
128
  )
117
129
  ids = query.all()
118
130
  return [str(id_tuple[0]) for id_tuple in ids]
@@ -120,25 +132,33 @@ class PGVectorAdapter(VectorStoreAdapter):
120
132
  logger.error(f"Failed to get indexed IDs from PGVector: {str(e)}")
121
133
  return []
122
134
 
123
- def clean_collection(self, vectorstore_wrapper, collection_suffix: str = ''):
135
+ def clean_collection(self, vectorstore_wrapper, index_name: str = ''):
124
136
  """Clean the vectorstore collection by deleting all indexed data."""
125
137
  # This logic deletes all data from the vectorstore collection without removal of collection.
126
138
  # Collection itself remains available for future indexing.
127
- vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, collection_suffix))
139
+ from sqlalchemy.orm import Session
140
+ from sqlalchemy import func
141
+
142
+ store = vectorstore_wrapper.vectorstore
143
+ with Session(store.session_maker.bind) as session:
144
+ session.query(store.EmbeddingStore).filter(
145
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
146
+ ).delete(synchronize_session=False)
147
+ session.commit()
128
148
 
129
149
  def is_vectorstore_type(self, vectorstore) -> bool:
130
150
  """Check if the vectorstore is a PGVector store."""
131
151
  return hasattr(vectorstore, 'session_maker') and hasattr(vectorstore, 'EmbeddingStore')
132
152
 
133
- def get_indexed_data(self, vectorstore_wrapper, collection_suffix: str)-> Dict[str, Dict[str, Any]]:
134
- """Get all indexed data from PGVector for non-code content per collection_suffix."""
153
+ def get_indexed_data(self, vectorstore_wrapper, index_name: str)-> Dict[str, Dict[str, Any]]:
154
+ """Get all indexed data from PGVector for non-code content per index_name."""
135
155
  from sqlalchemy.orm import Session
136
156
  from sqlalchemy import func
137
157
  from ...runtime.utils.utils import IndexerKeywords
138
158
 
139
159
  result = {}
140
160
  try:
141
- vectorstore_wrapper._log_data("Retrieving already indexed data from PGVector vectorstore",
161
+ vectorstore_wrapper._log_tool_event("Retrieving already indexed data from PGVector vectorstore",
142
162
  tool_name="get_indexed_data")
143
163
  store = vectorstore_wrapper.vectorstore
144
164
  with Session(store.session_maker.bind) as session:
@@ -147,7 +167,7 @@ class PGVectorAdapter(VectorStoreAdapter):
147
167
  store.EmbeddingStore.document,
148
168
  store.EmbeddingStore.cmetadata
149
169
  ).filter(
150
- func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == collection_suffix
170
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
151
171
  ).all()
152
172
 
153
173
  # Process the retrieved data
@@ -180,14 +200,14 @@ class PGVectorAdapter(VectorStoreAdapter):
180
200
 
181
201
  return result
182
202
 
183
- def get_code_indexed_data(self, vectorstore_wrapper, collection_suffix: str) -> Dict[str, Dict[str, Any]]:
203
+ def get_code_indexed_data(self, vectorstore_wrapper, index_name: str) -> Dict[str, Dict[str, Any]]:
184
204
  """Get all indexed code data from PGVector per collection suffix."""
185
205
  from sqlalchemy.orm import Session
186
206
  from sqlalchemy import func
187
207
 
188
208
  result = {}
189
209
  try:
190
- vectorstore_wrapper._log_data("Retrieving already indexed code data from PGVector vectorstore",
210
+ vectorstore_wrapper._log_tool_event(message="Retrieving already indexed code data from PGVector vectorstore",
191
211
  tool_name="index_code_data")
192
212
  store = vectorstore_wrapper.vectorstore
193
213
  with (Session(store.session_maker.bind) as session):
@@ -195,7 +215,7 @@ class PGVectorAdapter(VectorStoreAdapter):
195
215
  store.EmbeddingStore.id,
196
216
  store.EmbeddingStore.cmetadata
197
217
  ).filter(
198
- func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == collection_suffix
218
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
199
219
  ).all()
200
220
 
201
221
  for db_id, meta in docs:
@@ -265,6 +285,29 @@ class PGVectorAdapter(VectorStoreAdapter):
265
285
  except Exception as e:
266
286
  logger.error(f"Failed to update collection for entry ID {entry_id}: {str(e)}")
267
287
 
288
+ def get_index_meta(self, vectorstore_wrapper, index_name: str) -> List[Dict[str, Any]]:
289
+ from sqlalchemy.orm import Session
290
+ from sqlalchemy import func
291
+
292
+ store = vectorstore_wrapper.vectorstore
293
+ try:
294
+ with Session(store.session_maker.bind) as session:
295
+ meta = session.query(
296
+ store.EmbeddingStore.id,
297
+ store.EmbeddingStore.document,
298
+ store.EmbeddingStore.cmetadata
299
+ ).filter(
300
+ store.EmbeddingStore.cmetadata['type'].astext == IndexerKeywords.INDEX_META_TYPE.value,
301
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
302
+ ).all()
303
+ result = []
304
+ for id, document, cmetadata in meta:
305
+ result.append({"id": id, "content": document, "metadata": cmetadata})
306
+ return result
307
+ except Exception as e:
308
+ logger.error(f"Failed to get index_meta from PGVector: {str(e)}")
309
+ raise e
310
+
268
311
 
269
312
  class ChromaAdapter(VectorStoreAdapter):
270
313
  """Adapter for Chroma database operations."""
@@ -282,7 +325,7 @@ class ChromaAdapter(VectorStoreAdapter):
282
325
  def remove_collection(self, vectorstore_wrapper, collection_name: str):
283
326
  vectorstore_wrapper.vectorstore.delete_collection()
284
327
 
285
- def get_indexed_ids(self, vectorstore_wrapper, collection_suffix: Optional[str] = '') -> List[str]:
328
+ def get_indexed_ids(self, vectorstore_wrapper, index_name: Optional[str] = '') -> List[str]:
286
329
  """Get all indexed document IDs from Chroma"""
287
330
  try:
288
331
  data = vectorstore_wrapper.vectorstore.get(include=[]) # Only get IDs, no metadata
@@ -291,9 +334,9 @@ class ChromaAdapter(VectorStoreAdapter):
291
334
  logger.error(f"Failed to get indexed IDs from Chroma: {str(e)}")
292
335
  return []
293
336
 
294
- def clean_collection(self, vectorstore_wrapper, collection_suffix: str = ''):
337
+ def clean_collection(self, vectorstore_wrapper, index_name: str = ''):
295
338
  """Clean the vectorstore collection by deleting all indexed data."""
296
- vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, collection_suffix))
339
+ vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, index_name))
297
340
 
298
341
  def get_indexed_data(self, vectorstore_wrapper):
299
342
  """Get all indexed data from Chroma for non-code content"""
@@ -331,7 +374,7 @@ class ChromaAdapter(VectorStoreAdapter):
331
374
 
332
375
  return result
333
376
 
334
- def get_code_indexed_data(self, vectorstore_wrapper, collection_suffix) -> Dict[str, Dict[str, Any]]:
377
+ def get_code_indexed_data(self, vectorstore_wrapper, index_name) -> Dict[str, Dict[str, Any]]:
335
378
  """Get all indexed code data from Chroma."""
336
379
  result = {}
337
380
  try:
@@ -361,6 +404,9 @@ class ChromaAdapter(VectorStoreAdapter):
361
404
  # This is a simplified implementation - in practice, you might need more complex logic
362
405
  logger.warning("add_to_collection for Chroma is not fully implemented yet")
363
406
 
407
+ def get_index_meta(self, vectorstore_wrapper, index_name: str) -> List[Dict[str, Any]]:
408
+ logger.warning("get_index_meta for Chroma is not implemented yet")
409
+
364
410
 
365
411
  class VectorStoreAdapterFactory:
366
412
  """Factory for creating vector store adapters."""
@@ -56,7 +56,8 @@ class XrayToolkit(BaseToolkit):
56
56
  {
57
57
  'metadata': {
58
58
  "label": "XRAY cloud", "icon_url": "xray.svg",
59
- "categories": ["test management"],
59
+ "max_length": XrayToolkit.toolkit_max_length,
60
+ "categories": ["test management"],
60
61
  "extra_categories": ["test automation", "test case management", "test planning"]
61
62
  }
62
63
  }
@@ -31,7 +31,7 @@ class ZephyrToolkit(BaseToolkit):
31
31
  ZephyrToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
32
32
  return create_model(
33
33
  name,
34
- base_url=(str, Field(description="Base URL", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': ZephyrToolkit.toolkit_max_length})),
34
+ base_url=(str, Field(description="Base URL")),
35
35
  username=(str, Field(description="Username")),
36
36
  password=(SecretStr, Field(description="Password", json_schema_extra={'secret': True})),
37
37
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
@@ -40,6 +40,7 @@ class ZephyrToolkit(BaseToolkit):
40
40
  {
41
41
  'metadata': {
42
42
  "label": "Zephyr", "icon_url": "zephyr.svg", "hidden": True,
43
+ "max_length": ZephyrToolkit.toolkit_max_length,
43
44
  "categories": ["test management"],
44
45
  "extra_categories": ["test automation", "test case management", "test planning"]
45
46
  }}}
@@ -49,6 +49,7 @@ class ZephyrEnterpriseToolkit(BaseToolkit):
49
49
  __config__=ConfigDict(json_schema_extra={
50
50
  'metadata': {
51
51
  "label": "Zephyr Enterprise", "icon_url": "zephyr.svg",
52
+ "max_length": ZephyrEnterpriseToolkit.toolkit_max_length,
52
53
  "categories": ["test management"],
53
54
  "extra_categories": ["test automation", "test case management", "test planning"]
54
55
  }})
@@ -46,6 +46,7 @@ class ZephyrEssentialToolkit(BaseToolkit):
46
46
  # embedder settings
47
47
  embedding_model=(Optional[str], Field(default=None, description="Embedding configuration.", json_schema_extra={'configuration_model': 'embedding'})),
48
48
  __config__={'json_schema_extra': {'metadata': {"label": "Zephyr Essential", "icon_url": "zephyr.svg",
49
+ "max_length": ZephyrEssentialToolkit.toolkit_max_length,
49
50
  "categories": ["test management"],
50
51
  "extra_categories": ["test automation", "test case management", "test planning"]
51
52
  }}}
@@ -56,6 +56,7 @@ class ZephyrScaleToolkit(BaseToolkit):
56
56
  'metadata': {
57
57
  "label": "Zephyr Scale",
58
58
  "icon_url": "zephyr.svg",
59
+ "max_length": ZephyrScaleToolkit.toolkit_max_length,
59
60
  "categories": ["test management"],
60
61
  "extra_categories": ["test automation", "test case management", "test planning"],
61
62
  }
@@ -34,6 +34,7 @@ class ZephyrSquadToolkit(BaseToolkit):
34
34
  secret_key=(SecretStr, Field(description="Generated secret key", json_schema_extra={'secret': True})),
35
35
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
36
36
  __config__={'json_schema_extra': {'metadata': {"label": "Zephyr Squad", "icon_url": "zephyr.svg",
37
+ "max_length": ZephyrSquadToolkit.toolkit_max_length,
37
38
  "categories": ["test management"],
38
39
  "extra_categories": ["test automation", "test case management", "test planning"]
39
40
  }}}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.365
3
+ Version: 0.3.462
4
4
  Summary: SDK for building langchain agents using resources from Alita
5
5
  Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -18,12 +18,13 @@ Requires-Dist: python-dotenv~=1.0.1
18
18
  Requires-Dist: jinja2~=3.1.3
19
19
  Requires-Dist: pillow~=11.1.0
20
20
  Requires-Dist: requests~=2.3
21
- Requires-Dist: pydantic~=2.10.0
21
+ Requires-Dist: pydantic~=2.12.0
22
22
  Requires-Dist: chardet==5.2.0
23
23
  Requires-Dist: fastapi==0.115.9
24
24
  Requires-Dist: httpcore==1.0.7
25
25
  Requires-Dist: urllib3>=2
26
26
  Requires-Dist: certifi==2024.8.30
27
+ Requires-Dist: aiohttp>=3.9.0
27
28
  Provides-Extra: runtime
28
29
  Requires-Dist: streamlit>=1.28.0; extra == "runtime"
29
30
  Requires-Dist: langchain_core<0.4.0,>=0.3.76; extra == "runtime"
@@ -143,6 +144,11 @@ Requires-Dist: pytest-cov; extra == "dev"
143
144
  Requires-Dist: black; extra == "dev"
144
145
  Requires-Dist: flake8; extra == "dev"
145
146
  Requires-Dist: mypy; extra == "dev"
147
+ Provides-Extra: cli
148
+ Requires-Dist: click>=8.1.0; extra == "cli"
149
+ Requires-Dist: rich>=13.0.0; extra == "cli"
150
+ Requires-Dist: pyyaml>=6.0; extra == "cli"
151
+ Requires-Dist: langchain-mcp-adapters; extra == "cli"
146
152
  Dynamic: license-file
147
153
 
148
154
  Alita SDK