alita-sdk 0.3.263__py3-none-any.whl → 0.3.499__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 (248) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3601 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1256 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +64 -8
  30. alita_sdk/community/inventory/__init__.py +224 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/visualize.py +1370 -0
  58. alita_sdk/configurations/__init__.py +10 -0
  59. alita_sdk/configurations/ado.py +4 -2
  60. alita_sdk/configurations/azure_search.py +1 -1
  61. alita_sdk/configurations/bigquery.py +1 -1
  62. alita_sdk/configurations/bitbucket.py +94 -2
  63. alita_sdk/configurations/browser.py +18 -0
  64. alita_sdk/configurations/carrier.py +19 -0
  65. alita_sdk/configurations/confluence.py +96 -1
  66. alita_sdk/configurations/delta_lake.py +1 -1
  67. alita_sdk/configurations/figma.py +0 -5
  68. alita_sdk/configurations/github.py +65 -1
  69. alita_sdk/configurations/gitlab.py +79 -0
  70. alita_sdk/configurations/google_places.py +17 -0
  71. alita_sdk/configurations/jira.py +103 -0
  72. alita_sdk/configurations/postman.py +1 -1
  73. alita_sdk/configurations/qtest.py +1 -3
  74. alita_sdk/configurations/report_portal.py +19 -0
  75. alita_sdk/configurations/salesforce.py +19 -0
  76. alita_sdk/configurations/service_now.py +1 -12
  77. alita_sdk/configurations/sharepoint.py +19 -0
  78. alita_sdk/configurations/sonar.py +18 -0
  79. alita_sdk/configurations/sql.py +20 -0
  80. alita_sdk/configurations/testio.py +18 -0
  81. alita_sdk/configurations/testrail.py +88 -0
  82. alita_sdk/configurations/xray.py +94 -1
  83. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  84. alita_sdk/configurations/zephyr_essential.py +95 -0
  85. alita_sdk/runtime/clients/artifact.py +12 -2
  86. alita_sdk/runtime/clients/client.py +235 -66
  87. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  88. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  89. alita_sdk/runtime/clients/sandbox_client.py +373 -0
  90. alita_sdk/runtime/langchain/assistant.py +123 -17
  91. alita_sdk/runtime/langchain/constants.py +8 -1
  92. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  93. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  94. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +8 -2
  96. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  97. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  98. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  99. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  100. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  102. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  103. alita_sdk/runtime/langchain/document_loaders/constants.py +187 -40
  104. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  105. alita_sdk/runtime/langchain/langraph_agent.py +406 -91
  106. alita_sdk/runtime/langchain/utils.py +51 -8
  107. alita_sdk/runtime/llms/preloaded.py +2 -6
  108. alita_sdk/runtime/models/mcp_models.py +61 -0
  109. alita_sdk/runtime/toolkits/__init__.py +26 -0
  110. alita_sdk/runtime/toolkits/application.py +9 -2
  111. alita_sdk/runtime/toolkits/artifact.py +19 -7
  112. alita_sdk/runtime/toolkits/datasource.py +13 -6
  113. alita_sdk/runtime/toolkits/mcp.py +780 -0
  114. alita_sdk/runtime/toolkits/planning.py +178 -0
  115. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  116. alita_sdk/runtime/toolkits/tools.py +214 -60
  117. alita_sdk/runtime/toolkits/vectorstore.py +9 -4
  118. alita_sdk/runtime/tools/__init__.py +22 -0
  119. alita_sdk/runtime/tools/application.py +16 -4
  120. alita_sdk/runtime/tools/artifact.py +312 -19
  121. alita_sdk/runtime/tools/function.py +100 -4
  122. alita_sdk/runtime/tools/graph.py +81 -0
  123. alita_sdk/runtime/tools/image_generation.py +212 -0
  124. alita_sdk/runtime/tools/llm.py +539 -180
  125. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  126. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  127. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  128. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  129. alita_sdk/runtime/tools/planning/models.py +246 -0
  130. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  131. alita_sdk/runtime/tools/router.py +2 -1
  132. alita_sdk/runtime/tools/sandbox.py +375 -0
  133. alita_sdk/runtime/tools/vectorstore.py +62 -63
  134. alita_sdk/runtime/tools/vectorstore_base.py +156 -85
  135. alita_sdk/runtime/utils/AlitaCallback.py +106 -20
  136. alita_sdk/runtime/utils/mcp_client.py +465 -0
  137. alita_sdk/runtime/utils/mcp_oauth.py +244 -0
  138. alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
  139. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  140. alita_sdk/runtime/utils/streamlit.py +41 -14
  141. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  142. alita_sdk/runtime/utils/utils.py +14 -0
  143. alita_sdk/tools/__init__.py +78 -35
  144. alita_sdk/tools/ado/__init__.py +0 -1
  145. alita_sdk/tools/ado/repos/__init__.py +10 -6
  146. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -11
  147. alita_sdk/tools/ado/test_plan/__init__.py +10 -7
  148. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -23
  149. alita_sdk/tools/ado/wiki/__init__.py +10 -11
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -28
  151. alita_sdk/tools/ado/work_item/__init__.py +10 -11
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +63 -10
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +10 -7
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -11
  155. alita_sdk/tools/azure_ai/search/__init__.py +11 -7
  156. alita_sdk/tools/base_indexer_toolkit.py +392 -86
  157. alita_sdk/tools/bitbucket/__init__.py +18 -11
  158. alita_sdk/tools/bitbucket/api_wrapper.py +52 -9
  159. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  160. alita_sdk/tools/browser/__init__.py +40 -16
  161. alita_sdk/tools/browser/crawler.py +3 -1
  162. alita_sdk/tools/browser/utils.py +15 -6
  163. alita_sdk/tools/carrier/__init__.py +17 -17
  164. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  165. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  166. alita_sdk/tools/chunkers/__init__.py +3 -1
  167. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  168. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  169. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  170. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  171. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  172. alita_sdk/tools/cloud/aws/__init__.py +9 -6
  173. alita_sdk/tools/cloud/azure/__init__.py +9 -6
  174. alita_sdk/tools/cloud/gcp/__init__.py +9 -6
  175. alita_sdk/tools/cloud/k8s/__init__.py +9 -6
  176. alita_sdk/tools/code/linter/__init__.py +7 -7
  177. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  178. alita_sdk/tools/code/sonar/__init__.py +18 -12
  179. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  180. alita_sdk/tools/confluence/__init__.py +14 -11
  181. alita_sdk/tools/confluence/api_wrapper.py +198 -58
  182. alita_sdk/tools/confluence/loader.py +10 -0
  183. alita_sdk/tools/custom_open_api/__init__.py +9 -4
  184. alita_sdk/tools/elastic/__init__.py +8 -7
  185. alita_sdk/tools/elitea_base.py +543 -64
  186. alita_sdk/tools/figma/__init__.py +10 -8
  187. alita_sdk/tools/figma/api_wrapper.py +352 -153
  188. alita_sdk/tools/github/__init__.py +13 -11
  189. alita_sdk/tools/github/api_wrapper.py +9 -26
  190. alita_sdk/tools/github/github_client.py +75 -12
  191. alita_sdk/tools/github/schemas.py +2 -1
  192. alita_sdk/tools/gitlab/__init__.py +11 -10
  193. alita_sdk/tools/gitlab/api_wrapper.py +135 -45
  194. alita_sdk/tools/gitlab_org/__init__.py +11 -9
  195. alita_sdk/tools/google/bigquery/__init__.py +12 -13
  196. alita_sdk/tools/google_places/__init__.py +18 -10
  197. alita_sdk/tools/jira/__init__.py +14 -8
  198. alita_sdk/tools/jira/api_wrapper.py +315 -168
  199. alita_sdk/tools/keycloak/__init__.py +8 -7
  200. alita_sdk/tools/localgit/local_git.py +56 -54
  201. alita_sdk/tools/memory/__init__.py +27 -11
  202. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  203. alita_sdk/tools/ocr/__init__.py +8 -7
  204. alita_sdk/tools/openapi/__init__.py +10 -1
  205. alita_sdk/tools/pandas/__init__.py +8 -7
  206. alita_sdk/tools/pandas/api_wrapper.py +7 -25
  207. alita_sdk/tools/postman/__init__.py +8 -10
  208. alita_sdk/tools/postman/api_wrapper.py +19 -8
  209. alita_sdk/tools/postman/postman_analysis.py +8 -1
  210. alita_sdk/tools/pptx/__init__.py +8 -9
  211. alita_sdk/tools/qtest/__init__.py +19 -13
  212. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  213. alita_sdk/tools/rally/__init__.py +10 -9
  214. alita_sdk/tools/report_portal/__init__.py +20 -15
  215. alita_sdk/tools/salesforce/__init__.py +19 -15
  216. alita_sdk/tools/servicenow/__init__.py +14 -11
  217. alita_sdk/tools/sharepoint/__init__.py +14 -13
  218. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  219. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  220. alita_sdk/tools/sharepoint/utils.py +8 -2
  221. alita_sdk/tools/slack/__init__.py +10 -7
  222. alita_sdk/tools/sql/__init__.py +19 -18
  223. alita_sdk/tools/sql/api_wrapper.py +71 -23
  224. alita_sdk/tools/testio/__init__.py +18 -12
  225. alita_sdk/tools/testrail/__init__.py +10 -10
  226. alita_sdk/tools/testrail/api_wrapper.py +213 -45
  227. alita_sdk/tools/utils/__init__.py +28 -4
  228. alita_sdk/tools/utils/content_parser.py +181 -61
  229. alita_sdk/tools/utils/text_operations.py +254 -0
  230. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  231. alita_sdk/tools/xray/__init__.py +12 -7
  232. alita_sdk/tools/xray/api_wrapper.py +58 -113
  233. alita_sdk/tools/zephyr/__init__.py +9 -6
  234. alita_sdk/tools/zephyr_enterprise/__init__.py +13 -8
  235. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +17 -7
  236. alita_sdk/tools/zephyr_essential/__init__.py +13 -9
  237. alita_sdk/tools/zephyr_essential/api_wrapper.py +289 -47
  238. alita_sdk/tools/zephyr_essential/client.py +6 -4
  239. alita_sdk/tools/zephyr_scale/__init__.py +10 -7
  240. alita_sdk/tools/zephyr_scale/api_wrapper.py +6 -2
  241. alita_sdk/tools/zephyr_squad/__init__.py +9 -6
  242. {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/METADATA +180 -33
  243. alita_sdk-0.3.499.dist-info/RECORD +433 -0
  244. alita_sdk-0.3.499.dist-info/entry_points.txt +2 -0
  245. alita_sdk-0.3.263.dist-info/RECORD +0 -342
  246. {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/WHEEL +0 -0
  247. {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/licenses/LICENSE +0 -0
  248. {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/top_level.txt +0 -0
@@ -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,13 +26,13 @@ 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 = ''):
33
- """Clean the vectorstore collection by deleting all indexed data."""
34
+ def clean_collection(self, vectorstore_wrapper, index_name: str = '', including_index_meta: bool = False):
35
+ """Clean the vectorstore collection by deleting all indexed data. If including_index_meta is True, skip the index_meta records."""
34
36
  pass
35
37
 
36
38
  @abstractmethod
@@ -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,15 +50,26 @@ 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."""
54
61
 
55
62
  def get_vectorstore_params(self, collection_name: str, connection_string: Optional[str] = None) -> Dict[str, Any]:
63
+ try:
64
+ from tools import this # pylint: disable=E0401,C0415
65
+ worker_config = this.for_module("indexer_worker").descriptor.config
66
+ except: # pylint: disable=W0702
67
+ worker_config = {}
68
+ #
56
69
  return {
57
70
  "use_jsonb": True,
58
71
  "collection_name": collection_name,
59
- "create_extension": True,
72
+ "create_extension": worker_config.get("pgvector_create_extension", True),
60
73
  "alita_sdk_options": {
61
74
  "target_schema": collection_name,
62
75
  },
@@ -93,20 +106,25 @@ class PGVectorAdapter(VectorStoreAdapter):
93
106
  session.commit()
94
107
  logger.info(f"Schema '{schema_name}' has been dropped.")
95
108
 
96
- 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]:
97
110
  """Get all indexed document IDs from PGVector"""
98
111
  from sqlalchemy.orm import Session
99
- from sqlalchemy import func
112
+ from sqlalchemy import func, or_
100
113
 
101
114
  store = vectorstore_wrapper.vectorstore
102
115
  try:
103
116
  with Session(store.session_maker.bind) as session:
104
117
  # Start building the query
105
118
  query = session.query(store.EmbeddingStore.id)
106
- # Apply filter only if collection_suffix is provided
107
- if collection_suffix:
119
+ # Apply filter only if index_name is provided
120
+ if index_name:
108
121
  query = query.filter(
109
- 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
+ )
110
128
  )
111
129
  ids = query.all()
112
130
  return [str(id_tuple[0]) for id_tuple in ids]
@@ -114,25 +132,37 @@ class PGVectorAdapter(VectorStoreAdapter):
114
132
  logger.error(f"Failed to get indexed IDs from PGVector: {str(e)}")
115
133
  return []
116
134
 
117
- def clean_collection(self, vectorstore_wrapper, collection_suffix: str = ''):
118
- """Clean the vectorstore collection by deleting all indexed data."""
119
- # This logic deletes all data from the vectorstore collection without removal of collection.
120
- # Collection itself remains available for future indexing.
121
- vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, collection_suffix))
135
+ def clean_collection(self, vectorstore_wrapper, index_name: str = '', including_index_meta: bool = False):
136
+ """Clean the vectorstore collection by deleting all indexed data. If including_index_meta is True, skip the index_meta records."""
137
+ from sqlalchemy.orm import Session
138
+ from sqlalchemy import func, or_
139
+ store = vectorstore_wrapper.vectorstore
140
+ with Session(store.session_maker.bind) as session:
141
+ if including_index_meta:
142
+ session.query(store.EmbeddingStore).filter(
143
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
144
+ ).delete(synchronize_session=False)
145
+ else:
146
+ session.query(store.EmbeddingStore).filter(
147
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name,
148
+ or_(func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'type').is_(None),
149
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'type') != IndexerKeywords.INDEX_META_TYPE.value)
150
+ ).delete(synchronize_session=False)
151
+ session.commit()
122
152
 
123
153
  def is_vectorstore_type(self, vectorstore) -> bool:
124
154
  """Check if the vectorstore is a PGVector store."""
125
155
  return hasattr(vectorstore, 'session_maker') and hasattr(vectorstore, 'EmbeddingStore')
126
156
 
127
- def get_indexed_data(self, vectorstore_wrapper, collection_suffix: str)-> Dict[str, Dict[str, Any]]:
128
- """Get all indexed data from PGVector for non-code content per collection_suffix."""
157
+ def get_indexed_data(self, vectorstore_wrapper, index_name: str)-> Dict[str, Dict[str, Any]]:
158
+ """Get all indexed data from PGVector for non-code content per index_name."""
129
159
  from sqlalchemy.orm import Session
130
160
  from sqlalchemy import func
131
161
  from ...runtime.utils.utils import IndexerKeywords
132
162
 
133
163
  result = {}
134
164
  try:
135
- vectorstore_wrapper._log_data("Retrieving already indexed data from PGVector vectorstore",
165
+ vectorstore_wrapper._log_tool_event("Retrieving already indexed data from PGVector vectorstore",
136
166
  tool_name="get_indexed_data")
137
167
  store = vectorstore_wrapper.vectorstore
138
168
  with Session(store.session_maker.bind) as session:
@@ -141,7 +171,7 @@ class PGVectorAdapter(VectorStoreAdapter):
141
171
  store.EmbeddingStore.document,
142
172
  store.EmbeddingStore.cmetadata
143
173
  ).filter(
144
- func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == collection_suffix
174
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
145
175
  ).all()
146
176
 
147
177
  # Process the retrieved data
@@ -174,14 +204,14 @@ class PGVectorAdapter(VectorStoreAdapter):
174
204
 
175
205
  return result
176
206
 
177
- def get_code_indexed_data(self, vectorstore_wrapper, collection_suffix: str) -> Dict[str, Dict[str, Any]]:
207
+ def get_code_indexed_data(self, vectorstore_wrapper, index_name: str) -> Dict[str, Dict[str, Any]]:
178
208
  """Get all indexed code data from PGVector per collection suffix."""
179
209
  from sqlalchemy.orm import Session
180
210
  from sqlalchemy import func
181
211
 
182
212
  result = {}
183
213
  try:
184
- vectorstore_wrapper._log_data("Retrieving already indexed code data from PGVector vectorstore",
214
+ vectorstore_wrapper._log_tool_event(message="Retrieving already indexed code data from PGVector vectorstore",
185
215
  tool_name="index_code_data")
186
216
  store = vectorstore_wrapper.vectorstore
187
217
  with (Session(store.session_maker.bind) as session):
@@ -189,7 +219,7 @@ class PGVectorAdapter(VectorStoreAdapter):
189
219
  store.EmbeddingStore.id,
190
220
  store.EmbeddingStore.cmetadata
191
221
  ).filter(
192
- func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == collection_suffix
222
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
193
223
  ).all()
194
224
 
195
225
  for db_id, meta in docs:
@@ -259,6 +289,29 @@ class PGVectorAdapter(VectorStoreAdapter):
259
289
  except Exception as e:
260
290
  logger.error(f"Failed to update collection for entry ID {entry_id}: {str(e)}")
261
291
 
292
+ def get_index_meta(self, vectorstore_wrapper, index_name: str) -> List[Dict[str, Any]]:
293
+ from sqlalchemy.orm import Session
294
+ from sqlalchemy import func
295
+
296
+ store = vectorstore_wrapper.vectorstore
297
+ try:
298
+ with Session(store.session_maker.bind) as session:
299
+ meta = session.query(
300
+ store.EmbeddingStore.id,
301
+ store.EmbeddingStore.document,
302
+ store.EmbeddingStore.cmetadata
303
+ ).filter(
304
+ store.EmbeddingStore.cmetadata['type'].astext == IndexerKeywords.INDEX_META_TYPE.value,
305
+ func.jsonb_extract_path_text(store.EmbeddingStore.cmetadata, 'collection') == index_name
306
+ ).all()
307
+ result = []
308
+ for id, document, cmetadata in meta:
309
+ result.append({"id": id, "content": document, "metadata": cmetadata})
310
+ return result
311
+ except Exception as e:
312
+ logger.error(f"Failed to get index_meta from PGVector: {str(e)}")
313
+ raise e
314
+
262
315
 
263
316
  class ChromaAdapter(VectorStoreAdapter):
264
317
  """Adapter for Chroma database operations."""
@@ -276,7 +329,7 @@ class ChromaAdapter(VectorStoreAdapter):
276
329
  def remove_collection(self, vectorstore_wrapper, collection_name: str):
277
330
  vectorstore_wrapper.vectorstore.delete_collection()
278
331
 
279
- def get_indexed_ids(self, vectorstore_wrapper, collection_suffix: Optional[str] = '') -> List[str]:
332
+ def get_indexed_ids(self, vectorstore_wrapper, index_name: Optional[str] = '') -> List[str]:
280
333
  """Get all indexed document IDs from Chroma"""
281
334
  try:
282
335
  data = vectorstore_wrapper.vectorstore.get(include=[]) # Only get IDs, no metadata
@@ -285,9 +338,9 @@ class ChromaAdapter(VectorStoreAdapter):
285
338
  logger.error(f"Failed to get indexed IDs from Chroma: {str(e)}")
286
339
  return []
287
340
 
288
- def clean_collection(self, vectorstore_wrapper, collection_suffix: str = ''):
289
- """Clean the vectorstore collection by deleting all indexed data."""
290
- vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, collection_suffix))
341
+ def clean_collection(self, vectorstore_wrapper, index_name: str = '', including_index_meta: bool = False):
342
+ """Clean the vectorstore collection by deleting all indexed data. including_index_meta is ignored."""
343
+ vectorstore_wrapper.vectorstore.delete(ids=self.get_indexed_ids(vectorstore_wrapper, index_name))
291
344
 
292
345
  def get_indexed_data(self, vectorstore_wrapper):
293
346
  """Get all indexed data from Chroma for non-code content"""
@@ -325,7 +378,7 @@ class ChromaAdapter(VectorStoreAdapter):
325
378
 
326
379
  return result
327
380
 
328
- def get_code_indexed_data(self, vectorstore_wrapper, collection_suffix) -> Dict[str, Dict[str, Any]]:
381
+ def get_code_indexed_data(self, vectorstore_wrapper, index_name) -> Dict[str, Dict[str, Any]]:
329
382
  """Get all indexed code data from Chroma."""
330
383
  result = {}
331
384
  try:
@@ -355,6 +408,9 @@ class ChromaAdapter(VectorStoreAdapter):
355
408
  # This is a simplified implementation - in practice, you might need more complex logic
356
409
  logger.warning("add_to_collection for Chroma is not fully implemented yet")
357
410
 
411
+ def get_index_meta(self, vectorstore_wrapper, index_name: str) -> List[Dict[str, Any]]:
412
+ logger.warning("get_index_meta for Chroma is not implemented yet")
413
+
358
414
 
359
415
  class VectorStoreAdapterFactory:
360
416
  """Factory for creating vector store adapters."""
@@ -7,7 +7,8 @@ from pydantic import create_model, BaseModel, Field
7
7
 
8
8
  from .api_wrapper import XrayApiWrapper
9
9
  from ..base.tool import BaseAction
10
- from ..utils import clean_string, get_max_toolkit_length, TOOLKIT_SPLITTER
10
+ from ..elitea_base import filter_missconfigured_index_tools
11
+ from ..utils import clean_string, get_max_toolkit_length
11
12
  from ...configurations.pgvector import PgVectorConfiguration
12
13
  from ...configurations.xray import XrayConfiguration
13
14
 
@@ -21,6 +22,7 @@ def get_tools(tool):
21
22
  limit=tool['settings'].get('limit', 20),
22
23
  verify_ssl=tool['settings'].get('verify_ssl', True),
23
24
  toolkit_name=tool.get('toolkit_name'),
25
+ llm=tool['settings'].get('llm', None),
24
26
  alita=tool['settings'].get('alita', None),
25
27
 
26
28
  # indexer settings
@@ -32,12 +34,10 @@ def get_tools(tool):
32
34
 
33
35
  class XrayToolkit(BaseToolkit):
34
36
  tools: List[BaseTool] = []
35
- toolkit_max_length: int = 0
36
37
 
37
38
  @staticmethod
38
39
  def toolkit_config_schema() -> BaseModel:
39
40
  selected_tools = {x['name']: x['args_schema'].schema() for x in XrayApiWrapper.model_construct().get_available_tools()}
40
- XrayToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
41
41
  return create_model(
42
42
  name,
43
43
  limit=(Optional[int], Field(description="Limit", default=100)),
@@ -54,7 +54,7 @@ class XrayToolkit(BaseToolkit):
54
54
  {
55
55
  'metadata': {
56
56
  "label": "XRAY cloud", "icon_url": "xray.svg",
57
- "categories": ["test management"],
57
+ "categories": ["test management"],
58
58
  "extra_categories": ["test automation", "test case management", "test planning"]
59
59
  }
60
60
  }
@@ -62,6 +62,7 @@ class XrayToolkit(BaseToolkit):
62
62
  )
63
63
 
64
64
  @classmethod
65
+ @filter_missconfigured_index_tools
65
66
  def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
66
67
  if selected_tools is None:
67
68
  selected_tools = []
@@ -72,17 +73,21 @@ class XrayToolkit(BaseToolkit):
72
73
  **(kwargs.get('pgvector_configuration') or {}),
73
74
  }
74
75
  xray_api_wrapper = XrayApiWrapper(**wrapper_payload)
75
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
76
76
  available_tools = xray_api_wrapper.get_available_tools()
77
77
  tools = []
78
78
  for tool in available_tools:
79
79
  if selected_tools:
80
80
  if tool["name"] not in selected_tools:
81
81
  continue
82
+ description = tool["description"]
83
+ if toolkit_name:
84
+ description = f"Toolkit: {toolkit_name}\n{description}"
85
+ description = description + "\nXray instance: " + xray_api_wrapper.base_url
86
+ description = description[:1000]
82
87
  tools.append(BaseAction(
83
88
  api_wrapper=xray_api_wrapper,
84
- name=prefix + tool["name"],
85
- description=tool["description"] + "\nXray instance: " + xray_api_wrapper.base_url,
89
+ name=tool["name"],
90
+ description=description,
86
91
  args_schema=tool["args_schema"]
87
92
  ))
88
93
  return cls(tools=tools)
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import hashlib
4
- from typing import Any, Dict, Generator, List, Optional
4
+ from typing import Any, Dict, Generator, List, Optional, Literal
5
5
 
6
6
  import requests
7
7
  from langchain_core.documents import Document
@@ -9,12 +9,9 @@ from langchain_core.tools import ToolException
9
9
  from pydantic import PrivateAttr, SecretStr, create_model, model_validator, Field
10
10
  from python_graphql_client import GraphqlClient
11
11
 
12
- from ..elitea_base import (
13
- BaseVectorStoreToolApiWrapper,
14
- extend_with_vector_tools,
15
- )
12
+ from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
13
+ from ..utils.available_tools_decorator import extend_with_parent_available_tools
16
14
  from ...runtime.utils.utils import IndexerKeywords
17
- from ..utils.content_parser import parse_file_content, load_content_from_bytes
18
15
 
19
16
  try:
20
17
  from alita_sdk.runtime.langchain.interfaces.llm_processor import get_embeddings
@@ -31,7 +28,7 @@ _get_tests_query = """query GetTests($jql: String!, $limit:Int!, $start: Int)
31
28
  limit
32
29
  results {
33
30
  issueId
34
- jira(fields: ["key", "summary", "created", "updated", "assignee.displayName", "reporter.displayName"])
31
+ jira(fields: ["key", "summary", "description", "created", "updated", "assignee.displayName", "reporter.displayName"])
35
32
  projectId
36
33
  testType {
37
34
  name
@@ -107,7 +104,7 @@ XrayCreateTest = create_model(
107
104
 
108
105
  XrayCreateTests = create_model(
109
106
  "XrayCreateTests",
110
- graphql_mutations=(list[str], Field(description="list of GraphQL mutations:\n" + _graphql_mutation_description))
107
+ graphql_mutations=(List[str], Field(description="list of GraphQL mutations:\n" + _graphql_mutation_description))
111
108
  )
112
109
 
113
110
  def _parse_tests(test_results) -> List[Any]:
@@ -120,7 +117,7 @@ def _parse_tests(test_results) -> List[Any]:
120
117
  return test_results
121
118
 
122
119
 
123
- class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
120
+ class XrayApiWrapper(NonCodeIndexerToolkit):
124
121
  _default_base_url: str = 'https://xray.cloud.getxray.app'
125
122
  base_url: str = ""
126
123
  client_id: str = None
@@ -147,7 +144,7 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
147
144
  client_id = values['client_id']
148
145
  client_secret = values['client_secret']
149
146
  # Authenticate to get the token
150
- values['base_url'] = values.get('base_url', '') or cls._default_base_url
147
+ values['base_url'] = values.get('base_url', '') or cls._default_base_url.default
151
148
  auth_url = f"{values['base_url']}/api/v1/authenticate"
152
149
  auth_data = {
153
150
  "client_id": client_id,
@@ -168,7 +165,7 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
168
165
  return ToolException(f"Please, check you credentials ({values['client_id']} / {masked_secret}). Unable")
169
166
  else:
170
167
  return ToolException(f"Authentication failed: {str(e)}")
171
- return values
168
+ return super().validate_toolkit(values)
172
169
 
173
170
  def __init__(self, **data):
174
171
  super().__init__(**data)
@@ -333,6 +330,7 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
333
330
 
334
331
  for test in tests_data:
335
332
  page_content = ""
333
+ content_structure = {}
336
334
  test_type_name = test.get("testType", {}).get("name", "").lower()
337
335
 
338
336
  attachment_ids = []
@@ -359,19 +357,16 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
359
357
  content_structure = {"steps": steps_content}
360
358
  if attachment_ids:
361
359
  content_structure["attachment_ids"] = sorted(attachment_ids)
362
- page_content = json.dumps(content_structure, indent=2)
363
360
 
364
361
  elif test_type_name == "cucumber" and test.get("gherkin"):
365
362
  content_structure = {"gherkin": test["gherkin"]}
366
363
  if attachment_ids:
367
364
  content_structure["attachment_ids"] = sorted(attachment_ids)
368
- page_content = json.dumps(content_structure, indent=2)
369
365
 
370
366
  elif test.get("unstructured"):
371
367
  content_structure = {"unstructured": test["unstructured"]}
372
368
  if attachment_ids:
373
369
  content_structure["attachment_ids"] = sorted(attachment_ids)
374
- page_content = json.dumps(content_structure, indent=2)
375
370
 
376
371
  metadata = {"doctype": self.doctype}
377
372
 
@@ -382,7 +377,12 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
382
377
 
383
378
  if "created" in jira_data:
384
379
  metadata["created_on"] = jira_data["created"]
385
-
380
+
381
+ if jira_data.get("description"):
382
+ content_structure["description"] = jira_data.get("description")
383
+
384
+ page_content = json.dumps(content_structure if content_structure.items() else "", indent=2)
385
+
386
386
  content_hash = hashlib.sha256(page_content.encode('utf-8')).hexdigest()[:16]
387
387
  metadata["updated_on"] = content_hash
388
388
 
@@ -407,11 +407,13 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
407
407
  if "attachments" in step and step["attachments"]:
408
408
  for attachment in step["attachments"]:
409
409
  if attachment and "id" in attachment and "filename" in attachment:
410
+ attachment['step_id'] = step['id']
410
411
  attachments_data.append(attachment)
411
412
  if attachments_data:
412
413
  metadata["_attachments_data"] = attachments_data
413
414
 
414
- yield Document(page_content=page_content, metadata=metadata)
415
+ metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = page_content.encode('utf-8')
416
+ yield Document(page_content='', metadata=metadata)
415
417
 
416
418
  except Exception as e:
417
419
  logger.error(f"Error processing test data: {e}")
@@ -430,14 +432,7 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
430
432
  Generator[Document, None, None]: A generator yielding processed Document objects with metadata.
431
433
  """
432
434
  try:
433
- if not getattr(self, '_include_attachments', False):
434
- yield document
435
- return
436
-
437
435
  attachments_data = document.metadata.get("_attachments_data", [])
438
- if not attachments_data:
439
- yield document
440
- return
441
436
 
442
437
  issue_id = document.metadata.get("id")
443
438
 
@@ -458,44 +453,33 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
458
453
  ).append(attachment_id)
459
454
 
460
455
  try:
461
- content = self._process_attachment(attachment)
462
- if not content or content.startswith("Attachment processing failed"):
463
- logger.warning(f"Skipping attachment {filename} due to processing failure")
464
- continue
456
+ attachment_metadata = {
457
+ 'id': str(attachment_id),
458
+ 'issue_key': document.metadata.get('key', ''),
459
+ 'issueId': str(issue_id),
460
+ 'projectId': document.metadata.get('projectId', ''),
461
+ 'source': f"xray_test_{issue_id}",
462
+ 'filename': filename,
463
+ 'download_link': attachment.get('downloadLink', ''),
464
+ 'entity_type': 'test_case_attachment',
465
+ 'step_id': attachment.get('step_id', ''),
466
+ 'key': document.metadata.get('key', ''),
467
+ IndexerKeywords.PARENT.value: document.metadata.get('id', str(issue_id)),
468
+ 'type': 'attachment',
469
+ 'doctype': self.doctype,
470
+ }
471
+ yield from self._process_attachment(attachment, attachment_metadata)
465
472
  except Exception as e:
466
473
  logger.error(f"Failed to process attachment {filename}: {str(e)}")
467
474
  continue
468
-
469
- attachment_metadata = {
470
- 'id': str(attachment_id),
471
- 'issue_key': document.metadata.get('key', ''),
472
- 'issueId': str(issue_id),
473
- 'projectId': document.metadata.get('projectId', ''),
474
- 'source': f"xray_test_{issue_id}",
475
- 'filename': filename,
476
- 'download_link': attachment.get('downloadLink', ''),
477
- 'entity_type': 'test_case_attachment',
478
- 'key': document.metadata.get('key', ''),
479
- IndexerKeywords.PARENT.value: document.metadata.get('id', str(issue_id)),
480
- 'type': 'attachment',
481
- 'doctype': self.doctype,
482
- }
483
-
484
- yield Document(
485
- page_content=content,
486
- metadata=attachment_metadata
487
- )
488
475
 
489
476
  if "_attachments_data" in document.metadata:
490
477
  del document.metadata["_attachments_data"]
491
478
 
492
- yield document
493
-
494
479
  except Exception as e:
495
480
  logger.error(f"Error processing document for attachments: {e}")
496
- yield document
497
481
 
498
- def _process_attachment(self, attachment: Dict[str, Any]) -> str:
482
+ def _process_attachment(self, attachment: Dict[str, Any], attachment_metadata) -> Generator[Document, None, None]:
499
483
  """
500
484
  Processes an attachment to extract its content.
501
485
 
@@ -508,38 +492,17 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
508
492
  try:
509
493
  download_link = attachment.get('downloadLink')
510
494
  filename = attachment.get('filename', '')
511
-
512
- if not download_link:
513
- return f"Attachment: {filename} (no download link available)"
514
495
 
515
496
  try:
516
497
  auth_token = self._ensure_auth_token()
517
498
  headers = {'Authorization': f'Bearer {auth_token}'}
518
499
  response = requests.get(download_link, headers=headers, timeout=30)
519
500
  response.raise_for_status()
520
-
521
- ext = f".{filename.split('.')[-1].lower()}" if filename and '.' in filename else ""
522
-
523
- if ext == '.pdf':
524
- content = parse_file_content(
525
- file_content=response.content,
526
- file_name=filename,
527
- llm=self.llm,
528
- is_capture_image=True
529
- )
530
- else:
531
- content = load_content_from_bytes(
532
- response.content,
533
- ext,
534
- llm=self.llm
535
- )
536
-
537
- if content:
538
- return f"filename: {filename}\ncontent: {content}"
539
- else:
540
- logger.warning(f"No content extracted from attachment {filename}")
541
- return f"filename: {filename}\ncontent: [No extractable content]"
542
-
501
+
502
+ yield from self._load_attachment(content=response.content,
503
+ file_name=filename,
504
+ attachment_metadata=attachment_metadata)
505
+
543
506
  except requests.RequestException as req_e:
544
507
  logger.error(f"Unable to download attachment {filename} with existing token: {req_e}")
545
508
 
@@ -560,23 +523,13 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
560
523
  fresh_headers = {'Authorization': f'Bearer {fresh_token}'}
561
524
  response = requests.get(download_link, headers=fresh_headers, timeout=60)
562
525
  response.raise_for_status()
563
-
564
- ext = f".{filename.split('.')[-1].lower()}" if filename and '.' in filename else ""
565
- content = parse_file_content(
566
- file_content=response.content,
567
- file_name=filename,
568
- llm=self.llm,
569
- is_capture_image=True
570
- ) if ext == '.pdf' else load_content_from_bytes(response.content, ext, llm=self.llm)
571
-
572
- if content:
573
- return f"filename: {filename}\ncontent: {content}"
574
- else:
575
- return f"filename: {filename}\ncontent: [Content extraction failed after re-auth]"
526
+
527
+ yield from self._load_attachment(content=response.content,
528
+ file_name=filename,
529
+ attachment_metadata=attachment_metadata)
576
530
 
577
531
  except Exception as reauth_e:
578
532
  logger.error(f"Re-authentication and retry failed for {filename}: {reauth_e}")
579
- return f"Attachment: {filename} (download failed: {str(req_e)}, re-auth failed: {str(reauth_e)})"
580
533
  else:
581
534
  try:
582
535
  auth_token = self._ensure_auth_token()
@@ -587,34 +540,29 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
587
540
  }
588
541
  response = requests.get(download_link, headers=fallback_headers, timeout=60)
589
542
  response.raise_for_status()
590
-
591
- ext = f".{filename.split('.')[-1].lower()}" if filename and '.' in filename else ""
592
- content = parse_file_content(
593
- file_content=response.content,
594
- file_name=filename,
595
- llm=self.llm,
596
- is_capture_image=True
597
- ) if ext == '.pdf' else load_content_from_bytes(response.content, ext, llm=self.llm)
598
-
599
- if content:
600
- return f"filename: {filename}\ncontent: {content}"
601
- else:
602
- return f"filename: {filename}\ncontent: [Content extraction failed after fallback]"
543
+
544
+ yield from self._load_attachment(content=response.content,
545
+ file_name=filename,
546
+ attachment_metadata=attachment_metadata)
603
547
 
604
548
  except Exception as fallback_e:
605
549
  logger.error(f"Fallback download also failed for {filename}: {fallback_e}")
606
- return f"Attachment: {filename} (download failed: {str(req_e)}, fallback failed: {str(fallback_e)})"
607
550
 
608
551
  except Exception as parse_e:
609
552
  logger.error(f"Unable to parse attachment {filename}: {parse_e}")
610
- return f"Attachment: {filename} (parsing failed: {str(parse_e)})"
611
553
 
612
554
  except Exception as e:
613
555
  logger.error(f"Error processing attachment: {e}")
614
- return f"Attachment processing failed: {str(e)}"
556
+
557
+ def _load_attachment(self, content, file_name, attachment_metadata) -> Generator[Document, None, None]:
558
+ attachment_metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = content
559
+ attachment_metadata[IndexerKeywords.CONTENT_FILE_NAME.value] = file_name
560
+ yield Document(page_content='', metadata=attachment_metadata)
615
561
 
616
562
  def _index_tool_params(self, **kwargs) -> dict[str, tuple[type, Field]]:
617
563
  return {
564
+ 'chunking_tool': (Literal['json', ''],
565
+ Field(description="Name of chunking tool for base document", default='json')),
618
566
  'jql': (Optional[str], Field(description="""JQL query for searching test cases in Xray.
619
567
 
620
568
  Standard JQL query syntax for filtering Xray test cases. Examples:
@@ -684,9 +632,9 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
684
632
  except Exception as e:
685
633
  raise ToolException(f"Unable to execute GraphQL due to error: {str(e)}")
686
634
 
687
- @extend_with_vector_tools
635
+ @extend_with_parent_available_tools
688
636
  def get_available_tools(self):
689
- tools = [
637
+ return [
690
638
  {
691
639
  "name": "get_tests",
692
640
  "description": self.get_tests.__doc__,
@@ -711,7 +659,4 @@ class XrayApiWrapper(BaseVectorStoreToolApiWrapper):
711
659
  "args_schema": XrayGrapql,
712
660
  "ref": self.execute_graphql,
713
661
  }
714
- ]
715
-
716
- tools.extend(self._get_vector_search_tools())
717
- return tools
662
+ ]