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.
Files changed (49) hide show
  1. alita_sdk/runtime/clients/artifact.py +18 -4
  2. alita_sdk/runtime/clients/client.py +3 -2
  3. alita_sdk/runtime/langchain/document_loaders/AlitaCSVLoader.py +2 -1
  4. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +3 -3
  5. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +8 -4
  6. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -1
  7. alita_sdk/runtime/langchain/langraph_agent.py +1 -1
  8. alita_sdk/runtime/toolkits/artifact.py +7 -3
  9. alita_sdk/runtime/toolkits/tools.py +8 -1
  10. alita_sdk/runtime/tools/application.py +2 -0
  11. alita_sdk/runtime/tools/artifact.py +65 -8
  12. alita_sdk/runtime/tools/vectorstore.py +125 -41
  13. alita_sdk/runtime/utils/utils.py +3 -0
  14. alita_sdk/tools/ado/__init__.py +8 -0
  15. alita_sdk/tools/ado/repos/repos_wrapper.py +37 -0
  16. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +0 -7
  17. alita_sdk/tools/ado/work_item/__init__.py +4 -0
  18. alita_sdk/tools/ado/work_item/ado_wrapper.py +37 -4
  19. alita_sdk/tools/aws/delta_lake/__init__.py +1 -1
  20. alita_sdk/tools/bitbucket/__init__.py +13 -1
  21. alita_sdk/tools/bitbucket/api_wrapper.py +31 -4
  22. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +31 -0
  23. alita_sdk/tools/chunkers/code/codeparser.py +18 -10
  24. alita_sdk/tools/confluence/api_wrapper.py +35 -134
  25. alita_sdk/tools/confluence/loader.py +30 -28
  26. alita_sdk/tools/elitea_base.py +112 -11
  27. alita_sdk/tools/figma/__init__.py +13 -1
  28. alita_sdk/tools/figma/api_wrapper.py +47 -3
  29. alita_sdk/tools/github/api_wrapper.py +8 -0
  30. alita_sdk/tools/github/github_client.py +18 -0
  31. alita_sdk/tools/gitlab/__init__.py +4 -0
  32. alita_sdk/tools/gitlab/api_wrapper.py +10 -0
  33. alita_sdk/tools/google/bigquery/__init__.py +1 -1
  34. alita_sdk/tools/jira/__init__.py +21 -13
  35. alita_sdk/tools/jira/api_wrapper.py +285 -5
  36. alita_sdk/tools/postman/api_wrapper.py +27 -3
  37. alita_sdk/tools/sharepoint/__init__.py +11 -1
  38. alita_sdk/tools/sharepoint/api_wrapper.py +23 -53
  39. alita_sdk/tools/testrail/__init__.py +4 -0
  40. alita_sdk/tools/testrail/api_wrapper.py +21 -54
  41. alita_sdk/tools/utils/content_parser.py +72 -8
  42. alita_sdk/tools/xray/__init__.py +8 -1
  43. alita_sdk/tools/xray/api_wrapper.py +505 -14
  44. alita_sdk/tools/zephyr_scale/api_wrapper.py +5 -5
  45. {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/METADATA +1 -1
  46. {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/RECORD +49 -49
  47. {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/WHEEL +0 -0
  48. {alita_sdk-0.3.209.dist-info → alita_sdk-0.3.211.dist-info}/licenses/LICENSE +0 -0
  49. {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, BaseIndexParams
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
- cases = extracted_cases.get("cases")
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
- 'updated_on': case.get('updated_on') or -1,
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
- IndexerKeywords.PARENT.value: case_id,
638
- 'id': attachment_id,
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
- result = []
119
- for sheet_name, df in dfs.items():
120
- df.fillna('', inplace=True)
121
- result.append(f"=== Sheet: {sheet_name} ===\n{df.to_string(index=False)}")
122
- return "\n\n".join(result)
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.
@@ -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