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
@@ -24,7 +24,8 @@ from msrest.authentication import BasicAuthentication
24
24
  from pydantic import Field, PrivateAttr, create_model, model_validator, SecretStr
25
25
 
26
26
  from ..utils import extract_old_new_pairs, generate_diff, get_content_from_generator
27
- from ...elitea_base import BaseCodeToolApiWrapper
27
+ from ...code_indexer_toolkit import CodeIndexerToolkit
28
+ from ...utils.available_tools_decorator import extend_with_parent_available_tools
28
29
 
29
30
  logger = logging.getLogger(__name__)
30
31
 
@@ -110,8 +111,7 @@ class ArgsSchema(Enum):
110
111
  Field(
111
112
  description=(
112
113
  "Branch to be used for read file operation."
113
- ),
114
- default=None
114
+ )
115
115
  ),
116
116
  )
117
117
  )
@@ -159,6 +159,7 @@ class ArgsSchema(Enum):
159
159
  Field(
160
160
  description="""List of comments, where each comment is a dictionary specifying details about the comment,
161
161
  e.g. [{'file_path': 'src/main.py', 'comment_text': 'Logic needs improvement', 'right_line': 20}]""",
162
+ default=None,
162
163
  examples=[
163
164
  {
164
165
  "file_path": "src/main.py",
@@ -241,7 +242,7 @@ class ArgsSchema(Enum):
241
242
  )
242
243
 
243
244
 
244
- class ReposApiWrapper(BaseCodeToolApiWrapper):
245
+ class ReposApiWrapper(CodeIndexerToolkit):
245
246
  # TODO use ado_repos_configuration fields
246
247
  organization_url: Optional[str]
247
248
  project: Optional[str]
@@ -292,7 +293,7 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
292
293
  if not branch_exists(active_branch):
293
294
  raise ToolException(f"The active branch '{active_branch}' does not exist.")
294
295
 
295
- return values
296
+ return super().validate_toolkit(values)
296
297
 
297
298
  def _get_commits(self, file_path: str, branch: str, top: int = None) -> List[GitCommitRef]:
298
299
  """
@@ -643,6 +644,9 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
643
644
 
644
645
  return dumps(data)
645
646
 
647
+ def download_file(self, path):
648
+ return b"".join(self._client.get_item_content(self.repository_id, path=path, project=self.project, download=True))
649
+
646
650
  def get_file_content(self, commit_id, path):
647
651
  version_descriptor = GitVersionDescriptor(
648
652
  version=commit_id, version_type="commit"
@@ -1170,9 +1174,10 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
1170
1174
  except Exception as e:
1171
1175
  return ToolException(f"Unable to retrieve commits due to error:\n{str(e)}")
1172
1176
 
1177
+ @extend_with_parent_available_tools
1173
1178
  def get_available_tools(self):
1174
1179
  """Return a list of available tools."""
1175
- tools = [
1180
+ return [
1176
1181
  {
1177
1182
  "ref": self.list_branches_in_repo,
1178
1183
  "name": "list_branches_in_repo",
@@ -1263,8 +1268,4 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
1263
1268
  "description": self.get_commits.__doc__,
1264
1269
  "args_schema": ArgsSchema.GetCommits.value,
1265
1270
  },
1266
- ] # Add vector search tools from base class (includes index_data + search tools)
1267
- vector_search_tools = self._get_vector_search_tools()
1268
- tools.extend(vector_search_tools)
1269
-
1270
- return tools
1271
+ ]
@@ -4,11 +4,13 @@ from langchain_core.tools import BaseTool, BaseToolkit
4
4
  from pydantic import create_model, BaseModel, Field
5
5
 
6
6
  import requests
7
+
8
+ from ...elitea_base import filter_missconfigured_index_tools
7
9
  from ....configurations.ado import AdoConfiguration
8
10
  from ....configurations.pgvector import PgVectorConfiguration
9
11
  from .test_plan_wrapper import TestPlanApiWrapper
10
12
  from ...base.tool import BaseAction
11
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
13
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
12
14
 
13
15
 
14
16
  name = "azure_devops_plans"
@@ -17,15 +19,12 @@ name_alias = "ado_plans"
17
19
 
18
20
  class AzureDevOpsPlansToolkit(BaseToolkit):
19
21
  tools: List[BaseTool] = []
20
- toolkit_max_length: int = 0
21
22
 
22
23
  @staticmethod
23
24
  def toolkit_config_schema() -> BaseModel:
24
25
  selected_tools = {x['name']: x['args_schema'].schema() for x in TestPlanApiWrapper.model_construct().get_available_tools()}
25
- AzureDevOpsPlansToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
26
26
  m = create_model(
27
27
  name_alias,
28
- name=(str, Field(description="Toolkit name", json_schema_extra={'toolkit_name': True, 'max_toolkit_length': AzureDevOpsPlansToolkit.toolkit_max_length})),
29
28
  ado_configuration=(AdoConfiguration, Field(description="Ado configuration", json_schema_extra={'configuration_types': ['ado']})),
30
29
  limit=(Optional[int], Field(description="ADO plans limit used for limitation of the list with results", default=5)),
31
30
  # indexer settings
@@ -79,6 +78,7 @@ class AzureDevOpsPlansToolkit(BaseToolkit):
79
78
  return m
80
79
 
81
80
  @classmethod
81
+ @filter_missconfigured_index_tools
82
82
  def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
83
83
  from os import environ
84
84
  if not environ.get('AZURE_DEVOPS_CACHE_DIR', None):
@@ -94,16 +94,19 @@ class AzureDevOpsPlansToolkit(BaseToolkit):
94
94
  azure_devops_api_wrapper = TestPlanApiWrapper(**wrapper_payload)
95
95
  available_tools = azure_devops_api_wrapper.get_available_tools()
96
96
  tools = []
97
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
98
97
  for tool in available_tools:
99
98
  if selected_tools:
100
99
  if tool["name"] not in selected_tools:
101
100
  continue
102
101
  print(tool)
102
+ description = tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}"
103
+ if toolkit_name:
104
+ description = f"{description}\nToolkit: {toolkit_name}"
105
+ description = description[:1000]
103
106
  tools.append(BaseAction(
104
107
  api_wrapper=azure_devops_api_wrapper,
105
- name=prefix + tool["name"],
106
- description=tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}",
108
+ name=tool["name"],
109
+ description=description,
107
110
  args_schema=tool["args_schema"]
108
111
  ))
109
112
  return cls(tools=tools)
@@ -1,7 +1,8 @@
1
1
  import json
2
2
  import logging
3
+ import re
3
4
  import xml.etree.ElementTree as ET
4
- from typing import Generator, Optional
5
+ from typing import Generator, Literal, Optional, List
5
6
 
6
7
  from azure.devops.connection import Connection
7
8
  from azure.devops.v7_0.test_plan.models import TestPlanCreateParams, TestSuiteCreateParams, \
@@ -14,7 +15,9 @@ from pydantic import create_model, PrivateAttr, model_validator, SecretStr
14
15
  from pydantic.fields import FieldInfo as Field
15
16
 
16
17
  from ..work_item import AzureDevOpsApiWrapper
17
- from ...elitea_base import BaseVectorStoreToolApiWrapper, extend_with_vector_tools
18
+ from ...non_code_indexer_toolkit import NonCodeIndexerToolkit
19
+ from ...utils.available_tools_decorator import extend_with_parent_available_tools
20
+ from ....runtime.utils.utils import IndexerKeywords
18
21
 
19
22
  logger = logging.getLogger(__name__)
20
23
 
@@ -158,7 +161,7 @@ TestCasesGetModel = create_model(
158
161
  suite_id=(int, Field(description="ID of the test suite for which test cases are requested"))
159
162
  )
160
163
 
161
- class TestPlanApiWrapper(BaseVectorStoreToolApiWrapper):
164
+ class TestPlanApiWrapper(NonCodeIndexerToolkit):
162
165
  # TODO use ado_configuration instead of organization_url, project and token
163
166
  __test__ = False
164
167
  organization_url: str
@@ -178,7 +181,7 @@ class TestPlanApiWrapper(BaseVectorStoreToolApiWrapper):
178
181
  cls._client = connection.clients.get_test_plan_client()
179
182
  except Exception as e:
180
183
  raise ImportError(f"Failed to connect to Azure DevOps: {e}")
181
- return values
184
+ return super().validate_toolkit(values)
182
185
 
183
186
  def create_test_plan(self, test_plan_create_params: str):
184
187
  """Create a test plan in Azure DevOps."""
@@ -275,7 +278,7 @@ class TestPlanApiWrapper(BaseVectorStoreToolApiWrapper):
275
278
  test_steps_format: str = 'json'):
276
279
  """Creates a new test case in specified suite in Azure DevOps."""
277
280
  work_item_wrapper = AzureDevOpsApiWrapper(organization_url=self.organization_url,
278
- token=self.token.get_secret_value(), project=self.project)
281
+ token=self.token.get_secret_value(), project=self.project, llm=self.llm)
279
282
  if test_steps_format == 'json':
280
283
  steps_xml = self.get_test_steps_xml(json.loads(test_steps))
281
284
  elif test_steps_format == 'xml':
@@ -357,40 +360,70 @@ class TestPlanApiWrapper(BaseVectorStoreToolApiWrapper):
357
360
  test_cases = self._client.get_test_case_list(self.project, plan_id, suite_id)
358
361
  return [test_case.as_dict() for test_case in test_cases]
359
362
  except Exception as e:
363
+ self._log_tool_event(f"Error getting test cases: {e}", 'get_test_cases')
360
364
  logger.error(f"Error getting test cases: {e}")
361
365
  return ToolException(f"Error getting test cases: {e}")
362
366
 
363
- def _base_loader(self, plan_id: str, suite_ids: Optional[list[str]] = [], **kwargs) -> Generator[Document, None, None]:
367
+ def get_suites_in_plan(self, plan_id: int) -> List[dict]:
368
+ """Get all test suites in a test plan."""
369
+ try:
370
+ test_suites = self._client.get_test_suites_for_plan(self.project, plan_id)
371
+ return [suite.as_dict() for suite in test_suites]
372
+ except Exception as e:
373
+ logger.error(f"Error getting test suites: {e}")
374
+ return ToolException(f"Error getting test suites: {e}")
375
+
376
+ def _base_loader(self, plan_id: int, suite_ids: Optional[List[int]] = [], chunking_tool: str = None, **kwargs) -> Generator[Document, None, None]:
364
377
  cases = []
378
+ if not suite_ids:
379
+ suites = self.get_suites_in_plan(plan_id)
380
+ suite_ids = [suite['id'] for suite in suites if 'id' in suite]
365
381
  for sid in suite_ids:
366
382
  cases.extend(self.get_test_cases(plan_id, sid))
367
383
  #
368
384
  for case in cases:
369
385
  field_dicts = case.get('work_item', {}).get('work_item_fields', [])
370
386
  data = {k: v for d in field_dicts for k, v in d.items()}
371
- yield Document(
372
- page_content=data.get('Microsoft.VSTS.TCM.Steps', ''),
373
- metadata={
374
- 'id': case.get('work_item', {}).get('id', ''),
375
- 'title': case.get('work_item', {}).get('name', ''),
376
- 'plan_id': case.get('test_plan', {}).get('id', ''),
377
- 'suite_id': case.get('test_suite', {}).get('id', ''),
378
- 'description': data.get('System.Description', ''),
379
- 'updated_on': data.get('System.Rev', ''),
380
- })
381
-
382
- def _process_document(self, document: Document) -> Generator[Document, None, None]:
383
- if False:
384
- yield # Unreachable, but keeps the function a generator
387
+ if chunking_tool:
388
+ steps = data.get('Microsoft.VSTS.TCM.Steps', '')
389
+ # Remove XML declaration if present (like <?xml version="1.0" encoding="utf-16"?>) to avoid encoding issues
390
+ steps_no_decl = re.sub(r'<\?xml[^>]*\?>', '', steps, count=1).lstrip()
391
+
392
+ yield Document(
393
+ page_content='',
394
+ metadata={
395
+ 'id': case.get('work_item', {}).get('id', ''),
396
+ 'title': case.get('work_item', {}).get('name', ''),
397
+ 'plan_id': case.get('test_plan', {}).get('id', ''),
398
+ 'suite_id': case.get('test_suite', {}).get('id', ''),
399
+ 'description': data.get('System.Description', ''),
400
+ 'updated_on': data.get('System.Rev', ''),
401
+ # content is in metadata for chunking tool post-processing
402
+ IndexerKeywords.CONTENT_IN_BYTES.value: steps_no_decl.encode("utf-8")
403
+ })
404
+ else:
405
+ yield Document(
406
+ page_content=data.get('Microsoft.VSTS.TCM.Steps', ''),
407
+ metadata={
408
+ 'id': case.get('work_item', {}).get('id', ''),
409
+ 'title': case.get('work_item', {}).get('name', ''),
410
+ 'plan_id': case.get('test_plan', {}).get('id', ''),
411
+ 'suite_id': case.get('test_suite', {}).get('id', ''),
412
+ 'description': data.get('System.Description', ''),
413
+ 'updated_on': data.get('System.Rev', ''),
414
+ })
385
415
 
386
416
  def _index_tool_params(self):
387
417
  """Return the parameters for indexing data."""
388
418
  return {
389
- "plan_id": (str, Field(description="ID of the test plan for which test cases are requested")),
390
- "suite_ids": (str, Field(description="List of test suite IDs for which test cases are requested (can be empty)"))
419
+ 'chunking_tool': (Literal['xml', ''], Field(description="Name of chunking tool", default='xml')),
420
+ "plan_id": (int, Field(description="ID of the test plan for which test cases are requested")),
421
+ "suite_ids": (Optional[List[int]], Field(description='List of test suite IDs for which test cases are requested '
422
+ '(can be empty for all suites indexing from the plan). '
423
+ 'Example: [2, 23]', default=[])),
391
424
  }
392
425
 
393
- @extend_with_vector_tools
426
+ @extend_with_parent_available_tools
394
427
  def get_available_tools(self):
395
428
  """Return a list of available tools."""
396
429
  return [
@@ -4,29 +4,24 @@ from langchain_core.tools import BaseTool, BaseToolkit
4
4
  from pydantic import create_model, BaseModel, Field
5
5
 
6
6
  import requests
7
+
8
+ from ...elitea_base import filter_missconfigured_index_tools
7
9
  from ....configurations.ado import AdoConfiguration
8
10
  from ....configurations.pgvector import PgVectorConfiguration
9
11
  from ...base.tool import BaseAction
10
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
12
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
11
13
 
12
14
  name = "azure_devops_wiki"
13
15
  name_alias = 'ado_wiki'
14
16
 
15
17
  class AzureDevOpsWikiToolkit(BaseToolkit):
16
18
  tools: List[BaseTool] = []
17
- toolkit_max_length: int = 0
18
19
 
19
20
  @staticmethod
20
21
  def toolkit_config_schema() -> BaseModel:
21
22
  selected_tools = {x['name']: x['args_schema'].schema() for x in AzureDevOpsApiWrapper.model_construct().get_available_tools()}
22
- AzureDevOpsWikiToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
23
23
  m = create_model(
24
24
  name_alias,
25
- name=(str, Field(description="Toolkit name",
26
- json_schema_extra={
27
- 'toolkit_name': True,
28
- 'max_toolkit_length': AzureDevOpsWikiToolkit.toolkit_max_length})
29
- ),
30
25
  ado_configuration=(AdoConfiguration, Field(description="Ado configuration", json_schema_extra={'configuration_types': ['ado']})),
31
26
  # indexer settings
32
27
  pgvector_configuration=(Optional[PgVectorConfiguration], Field(default=None,
@@ -77,6 +72,7 @@ class AzureDevOpsWikiToolkit(BaseToolkit):
77
72
  return m
78
73
 
79
74
  @classmethod
75
+ @filter_missconfigured_index_tools
80
76
  def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
81
77
  from os import environ
82
78
  if not environ.get('AZURE_DEVOPS_CACHE_DIR', None):
@@ -92,15 +88,18 @@ class AzureDevOpsWikiToolkit(BaseToolkit):
92
88
  azure_devops_api_wrapper = AzureDevOpsApiWrapper(**wrapper_payload)
93
89
  available_tools = azure_devops_api_wrapper.get_available_tools()
94
90
  tools = []
95
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
96
91
  for tool in available_tools:
97
92
  if selected_tools:
98
93
  if tool["name"] not in selected_tools:
99
94
  continue
95
+ description = tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}"
96
+ if toolkit_name:
97
+ description = f"{description}\nToolkit: {toolkit_name}"
98
+ description = description[:1000]
100
99
  tools.append(BaseAction(
101
100
  api_wrapper=azure_devops_api_wrapper,
102
- name=prefix + tool["name"],
103
- description=tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}",
101
+ name=tool["name"],
102
+ description=description,
104
103
  args_schema=tool["args_schema"]
105
104
  ))
106
105
  return cls(tools=tools)
@@ -1,6 +1,8 @@
1
1
  import hashlib
2
2
  import logging
3
- from typing import Any, Dict, Generator, Optional
3
+ import re
4
+ import requests
5
+ from typing import Generator, Literal, Optional
4
6
 
5
7
  from azure.devops.connection import Connection
6
8
  from azure.devops.exceptions import AzureDevOpsServiceError
@@ -15,7 +17,12 @@ from pydantic import create_model, PrivateAttr, SecretStr
15
17
  from pydantic import model_validator
16
18
  from pydantic.fields import Field
17
19
 
18
- from ...elitea_base import BaseVectorStoreToolApiWrapper, extend_with_vector_tools
20
+ import alita_sdk.tools.ado.work_item
21
+ from ..repos import ReposApiWrapper
22
+ from ...non_code_indexer_toolkit import NonCodeIndexerToolkit
23
+ from ...utils.available_tools_decorator import extend_with_parent_available_tools
24
+ from ...utils.content_parser import parse_file_content
25
+ from ....runtime.utils.utils import IndexerKeywords
19
26
 
20
27
  logger = logging.getLogger(__name__)
21
28
 
@@ -27,13 +34,17 @@ GetWikiInput = create_model(
27
34
  GetPageByPathInput = create_model(
28
35
  "GetPageByPathInput",
29
36
  wiki_identified=(str, Field(description="Wiki ID or wiki name")),
30
- page_name=(str, Field(description="Wiki page path"))
37
+ page_name=(str, Field(description="Wiki page path")),
38
+ image_description_prompt=(Optional[str],
39
+ Field(description="Prompt which is used for image description", default=None))
31
40
  )
32
41
 
33
42
  GetPageByIdInput = create_model(
34
43
  "GetPageByIdInput",
35
44
  wiki_identified=(str, Field(description="Wiki ID or wiki name")),
36
- page_id=(int, Field(description="Wiki page ID"))
45
+ page_id=(int, Field(description="Wiki page ID")),
46
+ image_description_prompt=(Optional[str],
47
+ Field(description="Prompt which is used for image description", default=None))
37
48
  )
38
49
 
39
50
  ModifyPageInput = create_model(
@@ -41,8 +52,9 @@ ModifyPageInput = create_model(
41
52
  wiki_identified=(str, Field(description="Wiki ID or wiki name")),
42
53
  page_name=(str, Field(description="Wiki page name")),
43
54
  page_content=(str, Field(description="Wiki page content")),
44
- version_identifier=(str, Field(description="Version string identifier (name of tag/branch, SHA1 of commit)")),
45
- version_type=(Optional[str], Field(description="Version type (branch, tag, or commit). Determines how Id is interpreted", default="branch"))
55
+ version_identifier=(str, Field(description="Version string identifier (name of tag/branch, SHA1 of commit). Usually for wiki the branch is 'wikiMaster'")),
56
+ version_type=(Optional[str], Field(description="Version type (branch, tag, or commit). Determines how Id is interpreted", default="branch")),
57
+ expanded=(Optional[bool], Field(description="Whether to return the full page object or just its simplified version.", default=False))
46
58
  )
47
59
 
48
60
  RenamePageInput = create_model(
@@ -55,7 +67,20 @@ RenamePageInput = create_model(
55
67
  )
56
68
 
57
69
 
58
- class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
70
+ def _format_wiki_page_response(wiki_page_response, expanded: bool = False):
71
+ """Format wiki page response."""
72
+ try:
73
+ return {
74
+ 'eTag': wiki_page_response.eTag,
75
+ 'page': wiki_page_response.page.__dict__ if wiki_page_response.page else None
76
+ } if expanded else {"eTag": wiki_page_response.eTag, "id": wiki_page_response.page.id,
77
+ "page": wiki_page_response.page.url}
78
+ except:
79
+ logger.error(f"Unable to format wiki page response: {wiki_page_response}")
80
+ return wiki_page_response
81
+
82
+
83
+ class AzureDevOpsApiWrapper(NonCodeIndexerToolkit):
59
84
  # TODO use ado_configuration instead of organization_url, project and token
60
85
  organization_url: str
61
86
  project: str
@@ -82,7 +107,7 @@ class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
82
107
  except Exception as e:
83
108
  return ImportError(f"Failed to connect to Azure DevOps: {e}")
84
109
 
85
- return values
110
+ return super().validate_toolkit(values)
86
111
 
87
112
  def get_wiki(self, wiki_identified: str):
88
113
  """Extract ADO wiki information."""
@@ -92,24 +117,75 @@ class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
92
117
  logger.error(f"Error during the attempt to extract wiki: {str(e)}")
93
118
  return ToolException(f"Error during the attempt to extract wiki: {str(e)}")
94
119
 
95
- def get_wiki_page_by_path(self, wiki_identified: str, page_name: str):
120
+ def get_wiki_page_by_path(self, wiki_identified: str, page_name: str, image_description_prompt=None):
96
121
  """Extract ADO wiki page content."""
97
122
  try:
98
- return self._client.get_page(project=self.project, wiki_identifier=wiki_identified, path=page_name,
99
- include_content=True).page.content
123
+ return self._process_images(self._client.get_page(project=self.project, wiki_identifier=wiki_identified, path=page_name,
124
+ include_content=True).page.content,
125
+ image_description_prompt=image_description_prompt)
100
126
  except Exception as e:
101
127
  logger.error(f"Error during the attempt to extract wiki page: {str(e)}")
102
128
  return ToolException(f"Error during the attempt to extract wiki page: {str(e)}")
103
129
 
104
- def get_wiki_page_by_id(self, wiki_identified: str, page_id: int):
130
+ def get_wiki_page_by_id(self, wiki_identified: str, page_id: int, image_description_prompt=None):
105
131
  """Extract ADO wiki page content."""
106
132
  try:
107
- return (self._client.get_page_by_id(project=self.project, wiki_identifier=wiki_identified, id=page_id,
108
- include_content=True).page.content)
133
+ return self._process_images(self._client.get_page_by_id(project=self.project, wiki_identifier=wiki_identified, id=page_id,
134
+ include_content=True).page.content,
135
+ image_description_prompt=image_description_prompt)
109
136
  except Exception as e:
110
137
  logger.error(f"Error during the attempt to extract wiki page: {str(e)}")
111
138
  return ToolException(f"Error during the attempt to extract wiki page: {str(e)}")
112
139
 
140
+ def _process_images(self, page_content: str, image_description_prompt=None):
141
+
142
+ image_pattern = r"!\[(.*?)\]\((.*?)\)"
143
+ matches = re.findall(image_pattern, page_content)
144
+
145
+ for image_name, image_url in matches:
146
+ if image_url.startswith("/.attachments/"):
147
+ try:
148
+ description = self.process_attachment(attachment_url=image_url,
149
+ attachment_name=image_name,
150
+ image_description_prompt=image_description_prompt)
151
+ except Exception as e:
152
+ logger.error(f"Error parsing attachment: {str(e)}")
153
+ description = f"Error parsing attachment: {image_url}"
154
+ else:
155
+ try:
156
+ response = requests.get(image_url)
157
+ response.raise_for_status()
158
+ file_content = response.content
159
+ description = parse_file_content(
160
+ file_content=file_content,
161
+ file_name="image.png",
162
+ llm=self.llm,
163
+ prompt=image_description_prompt
164
+ )
165
+ except Exception as e:
166
+ logger.error(f"Error fetching external image: {str(e)}")
167
+ description = f"Error fetching external image: image_url"
168
+
169
+ new_image_markdown = f"![{image_name}]({description})"
170
+ page_content = page_content.replace(f"![{image_name}]({image_url})", new_image_markdown)
171
+ return page_content
172
+
173
+ def process_attachment(self, attachment_url, attachment_name, image_description_prompt):
174
+ wiki_master_branch = "wikiMaster"
175
+ repos_wrapper = ReposApiWrapper(organization_url=self.organization_url,
176
+ project=self.project,
177
+ token=self.token.get_secret_value(),
178
+ repository_id="Test_agent.wiki",
179
+ base_branch=wiki_master_branch,
180
+ active_branch=wiki_master_branch)
181
+ attachment_content = repos_wrapper.download_file(path=attachment_url)
182
+ return parse_file_content(
183
+ file_content=attachment_content,
184
+ file_name=attachment_name,
185
+ llm=self.llm,
186
+ prompt=image_description_prompt
187
+ )
188
+
113
189
  def delete_page_by_path(self, wiki_identified: str, page_name: str):
114
190
  """Extract ADO wiki page content."""
115
191
  try:
@@ -164,7 +240,7 @@ class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
164
240
  logger.error(f"Unable to rename wiki page: {str(e)}")
165
241
  return ToolException(f"Unable to rename wiki page: {str(e)}")
166
242
 
167
- def modify_wiki_page(self, wiki_identified: str, page_name: str, page_content: str, version_identifier: str, version_type: str = "branch"):
243
+ def modify_wiki_page(self, wiki_identified: str, page_name: str, page_content: str, version_identifier: str, version_type: str = "branch", expanded: Optional[bool] = False):
168
244
  """Create or Update ADO wiki page content."""
169
245
  try:
170
246
  all_wikis = [wiki.name for wiki in self._client.get_all_wikis(project=self.project)]
@@ -195,31 +271,31 @@ class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
195
271
  return ToolException(f"Unable to extract page by path {page_name}: {str(get_page_e)}")
196
272
 
197
273
  try:
198
- return self._client.create_or_update_page(
274
+ return _format_wiki_page_response(self._client.create_or_update_page(
199
275
  project=self.project,
200
276
  wiki_identifier=wiki_identified,
201
277
  path=page_name,
202
278
  parameters=WikiPageCreateOrUpdateParameters(content=page_content),
203
279
  version=version,
204
280
  version_descriptor=GitVersionDescriptor(version=version_identifier, version_type=version_type)
205
- )
281
+ ), expanded=expanded)
206
282
  except AzureDevOpsServiceError as e:
207
283
  if "The version '{0}' either is invalid or does not exist." in str(e):
208
284
  # Retry the request without version_descriptor
209
- return self._client.create_or_update_page(
285
+ return _format_wiki_page_response(wiki_page_response=self._client.create_or_update_page(
210
286
  project=self.project,
211
287
  wiki_identifier=wiki_identified,
212
288
  path=page_name,
213
289
  parameters=WikiPageCreateOrUpdateParameters(content=page_content),
214
290
  version=version
215
- )
291
+ ), expanded=expanded)
216
292
  else:
217
293
  raise
218
294
  except Exception as e:
219
295
  logger.error(f"Unable to modify wiki page: {str(e)}")
220
296
  return ToolException(f"Unable to modify wiki page: {str(e)}")
221
297
 
222
- def _base_loader(self, wiki_identifier: str, title_contains: Optional[str] = None, **kwargs) -> Generator[Document, None, None]:
298
+ def _base_loader(self, wiki_identifier: str, chunking_tool: str = None, title_contains: Optional[str] = None, **kwargs) -> Generator[Document, None, None]:
223
299
  pages = self._client.get_pages_batch(pages_batch_request={}, project=self.project, wiki_identifier=wiki_identifier)
224
300
  #
225
301
  for page in pages:
@@ -227,21 +303,31 @@ class AzureDevOpsApiWrapper(BaseVectorStoreToolApiWrapper):
227
303
  content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
228
304
  title = page.path.rsplit("/", 1)[-1]
229
305
  if not title_contains or (title_contains and title_contains.lower() in title.lower()):
230
- yield Document(page_content=content, metadata={
231
- 'id': str(page.id),
232
- 'path': page.path,
233
- 'title': title,
234
- 'updated_on': content_hash
235
- })
306
+ if chunking_tool:
307
+ yield Document(page_content='', metadata={
308
+ 'id': str(page.id),
309
+ 'path': page.path,
310
+ 'title': title,
311
+ 'updated_on': content_hash,
312
+ IndexerKeywords.CONTENT_IN_BYTES.value: content.encode("utf-8")
313
+ })
314
+ else:
315
+ yield Document(page_content=content, metadata={
316
+ 'id': str(page.id),
317
+ 'path': page.path,
318
+ 'title': title,
319
+ 'updated_on': content_hash
320
+ })
236
321
 
237
322
  def _index_tool_params(self):
238
323
  """Return the parameters for indexing data."""
239
324
  return {
325
+ 'chunking_tool': (Literal['markdown', ''], Field(description="Name of chunking tool", default='markdown')),
240
326
  "wiki_identifier": (str, Field(description="Wiki identifier to index, e.g., 'ABCProject.wiki'")),
241
- 'title_contains': (Optional[str], Field(default=None, description="Optional filter to include only pages with titles containing exact this string"))
327
+ 'title_contains': (Optional[str], Field(default=None, description="Optional filter to include only pages with titles containing exact this string")),
242
328
  }
243
329
 
244
- @extend_with_vector_tools
330
+ @extend_with_parent_available_tools
245
331
  def get_available_tools(self):
246
332
  """Return a list of available tools."""
247
333
  return [
@@ -4,28 +4,23 @@ from langchain_core.tools import BaseTool, BaseToolkit
4
4
  from pydantic import create_model, BaseModel, Field
5
5
 
6
6
  import requests
7
+
8
+ from ...elitea_base import filter_missconfigured_index_tools
7
9
  from ....configurations.ado import AdoConfiguration
8
10
  from ....configurations.pgvector import PgVectorConfiguration
9
11
  from ...base.tool import BaseAction
10
- from ...utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
12
+ from ...utils import clean_string, get_max_toolkit_length, check_connection_response
11
13
 
12
14
  name = "ado_boards"
13
15
 
14
16
  class AzureDevOpsWorkItemsToolkit(BaseToolkit):
15
17
  tools: List[BaseTool] = []
16
- toolkit_max_length: int = 0
17
18
 
18
19
  @staticmethod
19
20
  def toolkit_config_schema() -> BaseModel:
20
21
  selected_tools = {x['name']: x['args_schema'].schema() for x in AzureDevOpsApiWrapper.model_construct().get_available_tools()}
21
- AzureDevOpsWorkItemsToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
22
22
  m = create_model(
23
23
  name,
24
- name=(str, Field(description="Toolkit name",
25
- json_schema_extra={
26
- 'toolkit_name': True,
27
- 'max_toolkit_length': AzureDevOpsWorkItemsToolkit.toolkit_max_length})
28
- ),
29
24
  ado_configuration=(AdoConfiguration, Field(description="Ado Work Item configuration", json_schema_extra={'configuration_types': ['ado']})),
30
25
  limit=(Optional[int], Field(description="ADO plans limit used for limitation of the list with results", default=5)),
31
26
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
@@ -77,6 +72,7 @@ class AzureDevOpsWorkItemsToolkit(BaseToolkit):
77
72
  return m
78
73
 
79
74
  @classmethod
75
+ @filter_missconfigured_index_tools
80
76
  def get_toolkit(cls, selected_tools: list[str] | None = None, toolkit_name: Optional[str] = None, **kwargs):
81
77
  from os import environ
82
78
  if not environ.get('AZURE_DEVOPS_CACHE_DIR', None):
@@ -93,15 +89,18 @@ class AzureDevOpsWorkItemsToolkit(BaseToolkit):
93
89
  azure_devops_api_wrapper = AzureDevOpsApiWrapper(**wrapper_payload)
94
90
  available_tools = azure_devops_api_wrapper.get_available_tools()
95
91
  tools = []
96
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
97
92
  for tool in available_tools:
98
93
  if selected_tools:
99
94
  if tool["name"] not in selected_tools:
100
95
  continue
96
+ description = tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}"
97
+ if toolkit_name:
98
+ description = f"{description}\nToolkit: {toolkit_name}"
99
+ description = description[:1000]
101
100
  tools.append(BaseAction(
102
101
  api_wrapper=azure_devops_api_wrapper,
103
- name=prefix + tool["name"],
104
- description=tool["description"] + f"\nADO instance: {azure_devops_api_wrapper.organization_url}/{azure_devops_api_wrapper.project}",
102
+ name=tool["name"],
103
+ description=description,
105
104
  args_schema=tool["args_schema"]
106
105
  ))
107
106
  return cls(tools=tools)