alita-sdk 0.3.209__py3-none-any.whl → 0.3.211__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.
- alita_sdk/runtime/clients/artifact.py +18 -4
- alita_sdk/runtime/clients/client.py +3 -2
- alita_sdk/runtime/langchain/document_loaders/AlitaCSVLoader.py +2 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +3 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +8 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -1
- alita_sdk/runtime/langchain/langraph_agent.py +1 -1
- alita_sdk/runtime/toolkits/artifact.py +7 -3
- alita_sdk/runtime/toolkits/tools.py +8 -1
- alita_sdk/runtime/tools/application.py +2 -0
- alita_sdk/runtime/tools/artifact.py +65 -8
- alita_sdk/runtime/tools/vectorstore.py +125 -41
- alita_sdk/runtime/utils/utils.py +3 -0
- alita_sdk/tools/ado/__init__.py +8 -0
- alita_sdk/tools/ado/repos/repos_wrapper.py +37 -0
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +0 -7
- alita_sdk/tools/ado/work_item/__init__.py +4 -0
- alita_sdk/tools/ado/work_item/ado_wrapper.py +37 -4
- alita_sdk/tools/aws/delta_lake/__init__.py +1 -1
- alita_sdk/tools/bitbucket/__init__.py +13 -1
- alita_sdk/tools/bitbucket/api_wrapper.py +31 -4
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +31 -0
- alita_sdk/tools/chunkers/code/codeparser.py +18 -10
- alita_sdk/tools/confluence/api_wrapper.py +35 -134
- alita_sdk/tools/confluence/loader.py +30 -28
- alita_sdk/tools/elitea_base.py +112 -11
- alita_sdk/tools/figma/__init__.py +13 -1
- alita_sdk/tools/figma/api_wrapper.py +47 -3
- alita_sdk/tools/github/api_wrapper.py +8 -0
- alita_sdk/tools/github/github_client.py +18 -0
- alita_sdk/tools/gitlab/__init__.py +4 -0
- alita_sdk/tools/gitlab/api_wrapper.py +10 -0
- alita_sdk/tools/google/bigquery/__init__.py +1 -1
- alita_sdk/tools/jira/__init__.py +21 -13
- alita_sdk/tools/jira/api_wrapper.py +285 -5
- alita_sdk/tools/postman/api_wrapper.py +27 -3
- alita_sdk/tools/sharepoint/__init__.py +11 -1
- alita_sdk/tools/sharepoint/api_wrapper.py +23 -53
- alita_sdk/tools/testrail/__init__.py +4 -0
- alita_sdk/tools/testrail/api_wrapper.py +21 -54
- alita_sdk/tools/utils/content_parser.py +72 -8
- alita_sdk/tools/xray/__init__.py +8 -1
- alita_sdk/tools/xray/api_wrapper.py +505 -14
- alita_sdk/tools/zephyr_scale/api_wrapper.py +5 -5
- {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/RECORD +49 -49
- {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/top_level.txt +0 -0
@@ -4,10 +4,11 @@ from typing import Dict, List, Optional, Union, Any, Generator
|
|
4
4
|
|
5
5
|
import pandas as pd
|
6
6
|
from langchain_core.tools import ToolException
|
7
|
+
from openai import BadRequestError
|
7
8
|
from pydantic import SecretStr, create_model, model_validator
|
8
9
|
from pydantic.fields import Field, PrivateAttr
|
9
10
|
from testrail_api import StatusCodeError, TestRailAPI
|
10
|
-
from ..elitea_base import BaseVectorStoreToolApiWrapper,
|
11
|
+
from ..elitea_base import BaseVectorStoreToolApiWrapper, extend_with_vector_tools
|
11
12
|
from langchain_core.documents import Document
|
12
13
|
|
13
14
|
from ...runtime.utils.utils import IndexerKeywords
|
@@ -289,20 +290,6 @@ updateCase = create_model(
|
|
289
290
|
),
|
290
291
|
)
|
291
292
|
|
292
|
-
# Schema for indexing TestRail data into vector store
|
293
|
-
indexData = create_model(
|
294
|
-
"indexData",
|
295
|
-
__base__=BaseIndexParams,
|
296
|
-
project_id=(str, Field(description="TestRail project ID to index data from")),
|
297
|
-
suite_id=(Optional[str], Field(default=None, description="Optional TestRail suite ID to filter test cases")),
|
298
|
-
section_id=(Optional[int], Field(default=None, description="Optional section ID to filter test cases")),
|
299
|
-
title_keyword=(Optional[str], Field(default=None, description="Optional keyword to filter test cases by title")),
|
300
|
-
progress_step=(Optional[int],
|
301
|
-
Field(default=None, ge=0, le=100, description="Optional step size for progress reporting during indexing")),
|
302
|
-
clean_index=(Optional[bool],
|
303
|
-
Field(default=False, description="Optional flag to enforce clean existing index before indexing new data")),
|
304
|
-
)
|
305
|
-
|
306
293
|
SUPPORTED_KEYS = {
|
307
294
|
"id", "title", "section_id", "template_id", "type_id", "priority_id", "milestone_id",
|
308
295
|
"refs", "created_by", "created_on", "updated_by", "updated_on", "estimate",
|
@@ -317,14 +304,6 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
317
304
|
password: Optional[SecretStr] = None,
|
318
305
|
email: Optional[str] = None,
|
319
306
|
_client: Optional[TestRailAPI] = PrivateAttr() # Private attribute for the TestRail client
|
320
|
-
llm: Any = None
|
321
|
-
|
322
|
-
connection_string: Optional[SecretStr] = None
|
323
|
-
collection_name: Optional[str] = None
|
324
|
-
embedding_model: Optional[str] = "HuggingFaceEmbeddings"
|
325
|
-
embedding_model_params: Optional[Dict[str, Any]] = {"model_name": "sentence-transformers/all-MiniLM-L6-v2"}
|
326
|
-
vectorstore_type: Optional[str] = "PGVector"
|
327
|
-
|
328
307
|
|
329
308
|
@model_validator(mode="before")
|
330
309
|
@classmethod
|
@@ -490,7 +469,8 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
490
469
|
project_id=project_id, **params
|
491
470
|
)
|
492
471
|
|
493
|
-
|
472
|
+
# support old versions of testrail_api
|
473
|
+
cases = extracted_cases.get("cases") if isinstance(extracted_cases, dict) else extracted_cases
|
494
474
|
|
495
475
|
if cases is None:
|
496
476
|
return ToolException("No test cases found in the extracted data.")
|
@@ -554,7 +534,8 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
554
534
|
def _base_loader(self, project_id: str,
|
555
535
|
suite_id: Optional[str] = None,
|
556
536
|
section_id: Optional[int] = None,
|
557
|
-
title_keyword: Optional[str] = None
|
537
|
+
title_keyword: Optional[str] = None,
|
538
|
+
**kwargs: Any
|
558
539
|
) -> Generator[Document, None, None]:
|
559
540
|
try:
|
560
541
|
if suite_id:
|
@@ -577,7 +558,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
577
558
|
'title': case.get('title', ''),
|
578
559
|
'suite_id': suite_id or case.get('suite_id', ''),
|
579
560
|
'id': str(case.get('id', '')),
|
580
|
-
|
561
|
+
IndexerKeywords.UPDATED_ON.value: case.get('updated_on') or -1,
|
581
562
|
'labels': [lbl['title'] for lbl in case.get('labels', [])],
|
582
563
|
'type': case.get('type_id') or -1,
|
583
564
|
'priority': case.get('priority_id') or -1,
|
@@ -588,22 +569,6 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
588
569
|
'entity_type': 'test_case',
|
589
570
|
})
|
590
571
|
|
591
|
-
def index_data(
|
592
|
-
self,
|
593
|
-
project_id: str,
|
594
|
-
suite_id: Optional[str] = None,
|
595
|
-
collection_suffix: str = "",
|
596
|
-
section_id: Optional[int] = None,
|
597
|
-
title_keyword: Optional[str] = None,
|
598
|
-
progress_step: Optional[int] = None,
|
599
|
-
clean_index: Optional[bool] = False
|
600
|
-
):
|
601
|
-
"""Load TestRail test cases into the vector store."""
|
602
|
-
docs = self._base_loader(project_id, suite_id, section_id, title_keyword)
|
603
|
-
embedding = get_embeddings(self.embedding_model, self.embedding_model_params)
|
604
|
-
vs = self._init_vector_store(collection_suffix, embeddings=embedding)
|
605
|
-
return vs.index_documents(docs, progress_step=progress_step, clean_index=clean_index)
|
606
|
-
|
607
572
|
def _process_document(self, document: Document) -> Generator[Document, None, None]:
|
608
573
|
"""
|
609
574
|
Process an existing base document to extract relevant metadata for full document preparation.
|
@@ -626,16 +591,15 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
626
591
|
|
627
592
|
# process each attachment to extract its content
|
628
593
|
for attachment in attachments:
|
629
|
-
attachment_id = attachment['id']
|
594
|
+
attachment_id = f"attach_{attachment['id']}"
|
630
595
|
# add attachment id to metadata of parent
|
631
596
|
document.metadata.setdefault(IndexerKeywords.DEPENDENT_DOCS.value, []).append(attachment_id)
|
632
|
-
|
633
597
|
# TODO: pass it to chunkers
|
634
598
|
yield Document(page_content=self._process_attachment(attachment),
|
635
599
|
metadata={
|
636
600
|
'project_id': base_data.get('project_id', ''),
|
637
|
-
|
638
|
-
|
601
|
+
'id': str(attachment_id),
|
602
|
+
IndexerKeywords.PARENT.value: str(case_id),
|
639
603
|
'filename': attachment['filename'],
|
640
604
|
'filetype': attachment['filetype'],
|
641
605
|
'created_on': attachment['created_on'],
|
@@ -663,10 +627,20 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
663
627
|
try:
|
664
628
|
attachment_path = self._client.attachments.get_attachment(attachment_id=attachment['id'], path=f"./{attachment['filename']}")
|
665
629
|
page_content = parse_file_content(file_name=attachment['filename'], file_content=attachment_path.read_bytes(), llm=self.llm, is_capture_image=True)
|
630
|
+
except BadRequestError as ai_e:
|
631
|
+
logger.error(f"Unable to parse page's content with type: {attachment['filetype']} due to AI service issues: {ai_e}")
|
666
632
|
except Exception as e:
|
667
633
|
logger.error(f"Unable to parse page's content with type: {attachment['filetype']}: {e}")
|
668
634
|
return page_content
|
669
635
|
|
636
|
+
def _index_tool_params(self):
|
637
|
+
return {
|
638
|
+
'project_id': (str, Field(description="TestRail project ID to index data from")),
|
639
|
+
'suite_id': (Optional[str],
|
640
|
+
Field(default=None, description="Optional TestRail suite ID to filter test cases")),
|
641
|
+
'section_id': (Optional[int], Field(default=None, description="Optional section ID to filter test cases")),
|
642
|
+
}
|
643
|
+
|
670
644
|
def _to_markup(self, data: List[Dict], output_format: str) -> str:
|
671
645
|
"""
|
672
646
|
Converts the given data into the specified format: 'json', 'csv', or 'markdown'.
|
@@ -694,6 +668,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
694
668
|
if output_format == "markdown":
|
695
669
|
return df.to_markdown(index=False)
|
696
670
|
|
671
|
+
@extend_with_vector_tools
|
697
672
|
def get_available_tools(self):
|
698
673
|
tools = [
|
699
674
|
{
|
@@ -731,14 +706,6 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
731
706
|
"ref": self.update_case,
|
732
707
|
"description": self.update_case.__doc__,
|
733
708
|
"args_schema": updateCase,
|
734
|
-
},
|
735
|
-
{
|
736
|
-
"name": "index_data",
|
737
|
-
"ref": self.index_data,
|
738
|
-
"description": self.index_data.__doc__,
|
739
|
-
"args_schema": indexData,
|
740
709
|
}
|
741
710
|
]
|
742
|
-
# Add vector search from base
|
743
|
-
tools.extend(self._get_vector_search_tools())
|
744
711
|
return tools
|
@@ -61,7 +61,7 @@ IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp', 'svg']
|
|
61
61
|
|
62
62
|
|
63
63
|
def parse_file_content(file_name=None, file_content=None, is_capture_image: bool = False, page_number: int = None,
|
64
|
-
sheet_name: str = None, llm=None, file_path: str = None):
|
64
|
+
sheet_name: str = None, llm=None, file_path: str = None, excel_by_sheets: bool = False):
|
65
65
|
"""Parse the content of a file based on its type and return the parsed content.
|
66
66
|
|
67
67
|
Args:
|
@@ -91,7 +91,7 @@ def parse_file_content(file_name=None, file_content=None, is_capture_image: bool
|
|
91
91
|
elif file_name.endswith('.docx'):
|
92
92
|
return read_docx_from_bytes(file_content)
|
93
93
|
elif file_name.endswith('.xlsx') or file_name.endswith('.xls'):
|
94
|
-
return parse_excel(file_content, sheet_name)
|
94
|
+
return parse_excel(file_content, sheet_name, excel_by_sheets)
|
95
95
|
elif file_name.endswith('.pdf'):
|
96
96
|
return parse_pdf(file_content, page_number, is_capture_image, llm)
|
97
97
|
elif file_name.endswith('.pptx'):
|
@@ -109,17 +109,26 @@ def parse_txt(file_content):
|
|
109
109
|
except Exception as e:
|
110
110
|
return ToolException(f"Error decoding file content: {e}")
|
111
111
|
|
112
|
-
def parse_excel(file_content, sheet_name = None):
|
112
|
+
def parse_excel(file_content, sheet_name = None, return_by_sheets: bool = False):
|
113
113
|
try:
|
114
114
|
excel_file = io.BytesIO(file_content)
|
115
115
|
if sheet_name:
|
116
116
|
return parse_sheet(excel_file, sheet_name)
|
117
117
|
dfs = pd.read_excel(excel_file, sheet_name=sheet_name)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
|
119
|
+
if return_by_sheets:
|
120
|
+
result = {}
|
121
|
+
for sheet_name, df in dfs.items():
|
122
|
+
df.fillna('', inplace=True)
|
123
|
+
result[sheet_name] = df.to_dict(orient='records')
|
124
|
+
return result
|
125
|
+
else:
|
126
|
+
result = []
|
127
|
+
for sheet_name, df in dfs.items():
|
128
|
+
df.fillna('', inplace=True)
|
129
|
+
string_content = df.to_string(index=False)
|
130
|
+
result.append(f"====== Sheet name: {sheet_name} ======\n{string_content}")
|
131
|
+
return "\n\n".join(result)
|
123
132
|
except Exception as e:
|
124
133
|
return ToolException(f"Error reading Excel file: {e}")
|
125
134
|
|
@@ -194,6 +203,8 @@ def describe_image(image):
|
|
194
203
|
return "\n[Picture: " + processor.decode(out[0], skip_special_tokens=True) + "]\n"
|
195
204
|
|
196
205
|
def __perform_llm_prediction_for_image(llm, image: bytes, image_format='png', prompt=image_processing_prompt) -> str:
|
206
|
+
if not llm:
|
207
|
+
raise ToolException("LLM is not provided for image processing.")
|
197
208
|
base64_string = bytes_to_base64(image)
|
198
209
|
result = llm.invoke([
|
199
210
|
HumanMessage(
|
@@ -207,6 +218,59 @@ def __perform_llm_prediction_for_image(llm, image: bytes, image_format='png', pr
|
|
207
218
|
])
|
208
219
|
return f"\n[Image description: {result.content}]\n"
|
209
220
|
|
221
|
+
# TODO: review usage of this function alongside with functions above
|
222
|
+
def load_content(file_path: str, extension: str = None, loader_extra_config: dict = None, llm = None) -> str:
|
223
|
+
"""
|
224
|
+
Loads the content of a file based on its extension using a configured loader.
|
225
|
+
"""
|
226
|
+
try:
|
227
|
+
from ...runtime.langchain.document_loaders.constants import loaders_map
|
228
|
+
|
229
|
+
if not extension:
|
230
|
+
extension = file_path.split('.')[-1].lower()
|
231
|
+
|
232
|
+
loader_config = loaders_map.get(extension)
|
233
|
+
if not loader_config:
|
234
|
+
logger.warning(f"No loader found for file extension: {extension}. File: {file_path}")
|
235
|
+
return ""
|
236
|
+
|
237
|
+
loader_cls = loader_config['class']
|
238
|
+
loader_kwargs = loader_config['kwargs']
|
239
|
+
|
240
|
+
if loader_extra_config:
|
241
|
+
loader_kwargs.update(loader_extra_config)
|
242
|
+
if loader_config['is_multimodal_processing'] and llm:
|
243
|
+
loader_kwargs.update({'llm': llm})
|
244
|
+
|
245
|
+
loader = loader_cls(file_path, **loader_kwargs)
|
246
|
+
documents = loader.load()
|
247
|
+
|
248
|
+
page_contents = [doc.page_content for doc in documents]
|
249
|
+
return "\n".join(page_contents)
|
250
|
+
except Exception as e:
|
251
|
+
error_message = f"Error loading attachment: {str(e)}"
|
252
|
+
logger.warning(f"{error_message} for file {file_path}")
|
253
|
+
return ""
|
254
|
+
|
255
|
+
def load_content_from_bytes(file_content: bytes, extension: str = None, loader_extra_config: dict = None, llm = None) -> str:
|
256
|
+
"""Loads the content of a file from bytes based on its extension using a configured loader."""
|
257
|
+
|
258
|
+
import tempfile
|
259
|
+
|
260
|
+
# Automatic cleanup with context manager
|
261
|
+
with tempfile.NamedTemporaryFile(mode='w+b', delete=True) as temp_file:
|
262
|
+
# Write data to temp file
|
263
|
+
temp_file.write(file_content)
|
264
|
+
temp_file.flush() # Ensure data is written
|
265
|
+
|
266
|
+
# Get the file path for operations
|
267
|
+
temp_path = temp_file.name
|
268
|
+
|
269
|
+
# Perform your operations
|
270
|
+
return load_content(temp_path, extension, loader_extra_config, llm)
|
271
|
+
|
272
|
+
|
273
|
+
|
210
274
|
def file_to_bytes(filepath):
|
211
275
|
"""
|
212
276
|
Reads a file and returns its content as a bytes object.
|
alita_sdk/tools/xray/__init__.py
CHANGED
@@ -20,7 +20,14 @@ def get_tools(tool):
|
|
20
20
|
client_secret=tool['settings'].get('client_secret', None),
|
21
21
|
limit=tool['settings'].get('limit', 20),
|
22
22
|
verify_ssl=tool['settings'].get('verify_ssl', True),
|
23
|
-
toolkit_name=tool.get('toolkit_name')
|
23
|
+
toolkit_name=tool.get('toolkit_name'),
|
24
|
+
|
25
|
+
# indexer settings
|
26
|
+
connection_string=tool['settings'].get('connection_string', None),
|
27
|
+
collection_name=f"{tool.get('toolkit_name')}_{str(tool['id'])}",
|
28
|
+
embedding_model="HuggingFaceEmbeddings",
|
29
|
+
embedding_model_params={"model_name": "sentence-transformers/all-MiniLM-L6-v2"},
|
30
|
+
vectorstore_type="PGVector"
|
24
31
|
).get_tools()
|
25
32
|
|
26
33
|
|