alita-sdk 0.3.257__py3-none-any.whl → 0.3.584__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +155 -0
- alita_sdk/cli/agent_loader.py +215 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3794 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +11 -0
- alita_sdk/configurations/ado.py +148 -2
- alita_sdk/configurations/azure_search.py +1 -1
- alita_sdk/configurations/bigquery.py +1 -1
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/browser.py +18 -0
- alita_sdk/configurations/carrier.py +19 -0
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +76 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +81 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +323 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +72 -3
- alita_sdk/configurations/report_portal.py +115 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +167 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +101 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +94 -1
- alita_sdk/configurations/zephyr_enterprise.py +94 -1
- alita_sdk/configurations/zephyr_essential.py +95 -0
- alita_sdk/runtime/clients/artifact.py +21 -4
- alita_sdk/runtime/clients/client.py +458 -67
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +352 -0
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +183 -43
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
- alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
- alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
- alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +493 -105
- alita_sdk/runtime/langchain/utils.py +118 -8
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +28 -0
- alita_sdk/runtime/toolkits/application.py +14 -4
- alita_sdk/runtime/toolkits/artifact.py +25 -9
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +782 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +314 -70
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +24 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +367 -33
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +218 -0
- alita_sdk/runtime/tools/llm.py +1032 -177
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -1
- alita_sdk/runtime/tools/sandbox.py +375 -0
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +69 -65
- alita_sdk/runtime/tools/vectorstore_base.py +163 -90
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/streamlit.py +41 -14
- alita_sdk/runtime/utils/toolkit_utils.py +28 -9
- alita_sdk/runtime/utils/utils.py +48 -0
- alita_sdk/tools/__init__.py +135 -37
- alita_sdk/tools/ado/__init__.py +2 -2
- alita_sdk/tools/ado/repos/__init__.py +16 -19
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
- alita_sdk/tools/ado/test_plan/__init__.py +27 -8
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
- alita_sdk/tools/ado/wiki/__init__.py +28 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
- alita_sdk/tools/ado/work_item/__init__.py +28 -12
- alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +13 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +15 -11
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +14 -8
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +454 -110
- alita_sdk/tools/bitbucket/__init__.py +28 -19
- alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +41 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +18 -17
- alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
- alita_sdk/tools/carrier/excel_reporter.py +8 -4
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/codeparser.py +1 -1
- alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +12 -7
- alita_sdk/tools/cloud/azure/__init__.py +12 -7
- alita_sdk/tools/cloud/gcp/__init__.py +12 -7
- alita_sdk/tools/cloud/k8s/__init__.py +12 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +21 -13
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +22 -14
- alita_sdk/tools/confluence/api_wrapper.py +197 -58
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +546 -64
- alita_sdk/tools/figma/__init__.py +60 -11
- alita_sdk/tools/figma/api_wrapper.py +1400 -167
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +18 -17
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +81 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +19 -13
- alita_sdk/tools/gitlab/api_wrapper.py +256 -80
- alita_sdk/tools/gitlab_org/__init__.py +14 -10
- alita_sdk/tools/google/bigquery/__init__.py +14 -13
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +21 -11
- alita_sdk/tools/jira/__init__.py +22 -11
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +38 -14
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1357 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +40 -45
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/qtest/__init__.py +22 -14
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +13 -10
- alita_sdk/tools/report_portal/__init__.py +23 -16
- alita_sdk/tools/salesforce/__init__.py +22 -16
- alita_sdk/tools/servicenow/__init__.py +21 -16
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +17 -14
- alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +13 -8
- alita_sdk/tools/sql/__init__.py +22 -19
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +21 -13
- alita_sdk/tools/testrail/__init__.py +13 -11
- alita_sdk/tools/testrail/api_wrapper.py +214 -46
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +241 -55
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +18 -14
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +12 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +16 -9
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
- alita_sdk/tools/zephyr_essential/__init__.py +16 -10
- alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +13 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
- alita_sdk/tools/zephyr_squad/__init__.py +12 -7
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/METADATA +184 -37
- alita_sdk-0.3.584.dist-info/RECORD +452 -0
- alita_sdk-0.3.584.dist-info/entry_points.txt +2 -0
- alita_sdk/tools/bitbucket/tools.py +0 -304
- alita_sdk-0.3.257.dist-info/RECORD +0 -343
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Dict, List, Optional, Union, Any, Generator
|
|
3
|
+
from typing import Dict, List, Literal, Optional, Union, Any, Generator
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
+
from langchain_core.documents import Document
|
|
6
7
|
from langchain_core.tools import ToolException
|
|
7
8
|
from openai import BadRequestError
|
|
8
9
|
from pydantic import SecretStr, create_model, model_validator
|
|
9
10
|
from pydantic.fields import Field, PrivateAttr
|
|
10
11
|
from testrail_api import StatusCodeError, TestRailAPI
|
|
11
12
|
|
|
12
|
-
from ..chunkers.code.constants import get_file_extension
|
|
13
|
-
from ..
|
|
14
|
-
from
|
|
15
|
-
|
|
13
|
+
from ..chunkers.code.constants import get_file_extension, image_extensions
|
|
14
|
+
from ..non_code_indexer_toolkit import NonCodeIndexerToolkit
|
|
15
|
+
from ..utils.available_tools_decorator import extend_with_parent_available_tools
|
|
16
16
|
from ...runtime.utils.utils import IndexerKeywords
|
|
17
|
-
from ..utils.content_parser import parse_file_content
|
|
18
17
|
|
|
19
18
|
try:
|
|
20
19
|
from alita_sdk.runtime.langchain.interfaces.llm_processor import get_embeddings
|
|
@@ -118,6 +117,13 @@ getCases = create_model(
|
|
|
118
117
|
description="A list of case field keys to include in the data output. If None, defaults to ['title', 'id'].",
|
|
119
118
|
),
|
|
120
119
|
),
|
|
120
|
+
suite_id=(Optional[str],
|
|
121
|
+
Field(
|
|
122
|
+
default=None,
|
|
123
|
+
description="[Optional] Suite id for test cases extraction in case "
|
|
124
|
+
"project is in multiple suite mode (setting 3)",
|
|
125
|
+
),
|
|
126
|
+
),
|
|
121
127
|
)
|
|
122
128
|
|
|
123
129
|
getCasesByFilter = create_model(
|
|
@@ -292,6 +298,18 @@ updateCase = create_model(
|
|
|
292
298
|
),
|
|
293
299
|
)
|
|
294
300
|
|
|
301
|
+
getSuites = create_model(
|
|
302
|
+
"getSuites",
|
|
303
|
+
project_id=(str, Field(description="Project id")),
|
|
304
|
+
output_format=(
|
|
305
|
+
str,
|
|
306
|
+
Field(
|
|
307
|
+
default="json",
|
|
308
|
+
description="Desired output format. Supported values: 'json', 'csv', 'markdown'. Defaults to 'json'.",
|
|
309
|
+
),
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
|
|
295
313
|
SUPPORTED_KEYS = {
|
|
296
314
|
"id", "title", "section_id", "template_id", "type_id", "priority_id", "milestone_id",
|
|
297
315
|
"refs", "created_by", "created_on", "updated_by", "updated_on", "estimate",
|
|
@@ -301,7 +319,7 @@ SUPPORTED_KEYS = {
|
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
|
|
304
|
-
class TestrailAPIWrapper(
|
|
322
|
+
class TestrailAPIWrapper(NonCodeIndexerToolkit):
|
|
305
323
|
url: str
|
|
306
324
|
password: Optional[SecretStr] = None,
|
|
307
325
|
email: Optional[str] = None,
|
|
@@ -322,7 +340,76 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
322
340
|
password = values.get("password")
|
|
323
341
|
email = values.get("email")
|
|
324
342
|
cls._client = TestRailAPI(url, email, password)
|
|
325
|
-
return values
|
|
343
|
+
return super().validate_toolkit(values)
|
|
344
|
+
|
|
345
|
+
def _is_suite_id_required(self, project_id: str) -> bool:
|
|
346
|
+
"""
|
|
347
|
+
Returns True if project requires suite_id (multiple suite or baselines mode), otherwise False.
|
|
348
|
+
Args:
|
|
349
|
+
project_id: The TestRail project ID to check
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
project = self._client.projects.get_project(project_id=project_id)
|
|
353
|
+
# 1 for single suite mode, 2 for single suite + baselines, 3 for multiple suites
|
|
354
|
+
suite_mode = project.get('suite_mode', 1)
|
|
355
|
+
return suite_mode == 2 or suite_mode == 3
|
|
356
|
+
except StatusCodeError:
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
def _fetch_cases_with_suite_handling(
|
|
360
|
+
self,
|
|
361
|
+
project_id: str,
|
|
362
|
+
suite_id: Optional[str] = None,
|
|
363
|
+
**api_params
|
|
364
|
+
) -> List[Dict]:
|
|
365
|
+
"""
|
|
366
|
+
Unified method to fetch test cases with proper TestRail suite mode handling.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
project_id: The TestRail project ID
|
|
370
|
+
suite_id: Optional suite ID to filter by
|
|
371
|
+
**api_params: Additional parameters to pass to the get_cases API call
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List of test case dictionaries
|
|
375
|
+
"""
|
|
376
|
+
def _extract_cases_from_response(response):
|
|
377
|
+
"""Extract cases from API response, supporting both old and new testrail_api versions."""
|
|
378
|
+
return response.get('cases', []) if isinstance(response, dict) else response
|
|
379
|
+
|
|
380
|
+
suite_required = self._is_suite_id_required(project_id=project_id)
|
|
381
|
+
all_cases = []
|
|
382
|
+
|
|
383
|
+
if suite_required:
|
|
384
|
+
# Suite modes 2 & 3: Require suite_id parameter
|
|
385
|
+
if suite_id:
|
|
386
|
+
response = self._client.cases.get_cases(
|
|
387
|
+
project_id=project_id, suite_id=int(suite_id), **api_params
|
|
388
|
+
)
|
|
389
|
+
cases_from_suite = _extract_cases_from_response(response)
|
|
390
|
+
all_cases.extend(cases_from_suite)
|
|
391
|
+
else:
|
|
392
|
+
suites = self._get_raw_suites(project_id)
|
|
393
|
+
suite_ids = [suite['id'] for suite in suites if 'id' in suite]
|
|
394
|
+
for current_suite_id in suite_ids:
|
|
395
|
+
try:
|
|
396
|
+
response = self._client.cases.get_cases(
|
|
397
|
+
project_id=project_id, suite_id=int(current_suite_id), **api_params
|
|
398
|
+
)
|
|
399
|
+
cases_from_suite = _extract_cases_from_response(response)
|
|
400
|
+
all_cases.extend(cases_from_suite)
|
|
401
|
+
except StatusCodeError:
|
|
402
|
+
continue
|
|
403
|
+
else:
|
|
404
|
+
# Suite mode 1: Can fetch all cases directly without suite_id
|
|
405
|
+
try:
|
|
406
|
+
response = self._client.cases.get_cases(project_id=project_id, **api_params)
|
|
407
|
+
cases_from_project = _extract_cases_from_response(response)
|
|
408
|
+
all_cases.extend(cases_from_project)
|
|
409
|
+
except StatusCodeError as e:
|
|
410
|
+
logger.warning(f"Unable to fetch cases at project level: {e}")
|
|
411
|
+
|
|
412
|
+
return all_cases
|
|
326
413
|
|
|
327
414
|
def add_cases(self, add_test_cases_data: str):
|
|
328
415
|
"""Adds new test cases into Testrail per defined parameters.
|
|
@@ -390,7 +477,8 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
390
477
|
return f"Extracted test case:\n{str(extracted_case)}"
|
|
391
478
|
|
|
392
479
|
def get_cases(
|
|
393
|
-
self, project_id: str, output_format: str = "json", keys: Optional[List[str]] = None
|
|
480
|
+
self, project_id: str, output_format: str = "json", keys: Optional[List[str]] = None,
|
|
481
|
+
suite_id: Optional[str] = None
|
|
394
482
|
) -> Union[str, ToolException]:
|
|
395
483
|
"""
|
|
396
484
|
Extracts a list of test cases in the specified format: `json`, `csv`, or `markdown`.
|
|
@@ -411,10 +499,10 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
411
499
|
invalid_keys = [key for key in keys if key not in SUPPORTED_KEYS]
|
|
412
500
|
|
|
413
501
|
try:
|
|
414
|
-
|
|
415
|
-
cases =
|
|
502
|
+
# Use unified suite handling method
|
|
503
|
+
cases = self._fetch_cases_with_suite_handling(project_id=project_id, suite_id=suite_id)
|
|
416
504
|
|
|
417
|
-
if cases
|
|
505
|
+
if not cases:
|
|
418
506
|
return ToolException("No test cases found in the extracted data.")
|
|
419
507
|
|
|
420
508
|
extracted_cases_data = [
|
|
@@ -467,24 +555,36 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
467
555
|
"json_case_arguments must be a JSON string or dictionary."
|
|
468
556
|
)
|
|
469
557
|
self._log_tool_event(message=f"Extract test cases per filter {params}", tool_name='get_cases_by_filter')
|
|
470
|
-
|
|
471
|
-
|
|
558
|
+
|
|
559
|
+
# Extract suite_id from params for unified handling
|
|
560
|
+
suite_id_in_params = params.pop('suite_id', None)
|
|
561
|
+
suite_id = str(suite_id_in_params) if suite_id_in_params else None
|
|
562
|
+
|
|
563
|
+
# Use unified suite handling method with remaining filter parameters
|
|
564
|
+
cases = self._fetch_cases_with_suite_handling(
|
|
565
|
+
project_id=project_id,
|
|
566
|
+
suite_id=suite_id,
|
|
567
|
+
**params
|
|
472
568
|
)
|
|
473
|
-
self._log_tool_event(message=f"Test cases were extracted", tool_name='get_cases_by_filter')
|
|
474
|
-
# support old versions of testrail_api
|
|
475
|
-
cases = extracted_cases.get("cases") if isinstance(extracted_cases, dict) else extracted_cases
|
|
476
569
|
|
|
477
|
-
|
|
570
|
+
self._log_tool_event(message="Test cases were extracted", tool_name='get_cases_by_filter')
|
|
571
|
+
|
|
572
|
+
if not cases:
|
|
478
573
|
return ToolException("No test cases found in the extracted data.")
|
|
479
574
|
|
|
480
575
|
if keys is None:
|
|
481
576
|
return self._to_markup(cases, output_format)
|
|
482
577
|
|
|
483
|
-
extracted_cases_data = [
|
|
484
|
-
|
|
485
|
-
|
|
578
|
+
extracted_cases_data = []
|
|
579
|
+
for case in cases:
|
|
580
|
+
case_dict = {}
|
|
581
|
+
for key in keys:
|
|
582
|
+
if key in case:
|
|
583
|
+
case_dict[key] = case[key]
|
|
584
|
+
if case_dict:
|
|
585
|
+
extracted_cases_data.append(case_dict)
|
|
486
586
|
|
|
487
|
-
if extracted_cases_data
|
|
587
|
+
if not extracted_cases_data:
|
|
488
588
|
return ToolException("No valid test case data found to format.")
|
|
489
589
|
|
|
490
590
|
result = self._to_markup(extracted_cases_data, output_format)
|
|
@@ -532,47 +632,86 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
532
632
|
return (
|
|
533
633
|
f"Test case #{case_id} has been updated at '{updated_case['updated_on']}')"
|
|
534
634
|
)
|
|
635
|
+
|
|
636
|
+
def _get_raw_suites(self, project_id: str) -> List[Dict]:
|
|
637
|
+
"""
|
|
638
|
+
Internal helper to get raw suite data from TestRail API.
|
|
639
|
+
Handles both old and new testrail_api response formats.
|
|
640
|
+
"""
|
|
641
|
+
suites_response = self._client.suites.get_suites(project_id=project_id)
|
|
642
|
+
if isinstance(suites_response, dict) and 'suites' in suites_response:
|
|
643
|
+
return suites_response['suites']
|
|
644
|
+
else:
|
|
645
|
+
return suites_response if isinstance(suites_response, list) else []
|
|
646
|
+
|
|
647
|
+
def get_suites(self, project_id: str, output_format: str = "json",) -> Union[str, ToolException]:
|
|
648
|
+
"""Extracts a list of test suites for a given project from Testrail"""
|
|
649
|
+
try:
|
|
650
|
+
suites = self._get_raw_suites(project_id)
|
|
651
|
+
|
|
652
|
+
if not suites:
|
|
653
|
+
return ToolException("No test suites found for the specified project.")
|
|
654
|
+
|
|
655
|
+
suite_dicts = []
|
|
656
|
+
for suite in suites:
|
|
657
|
+
if isinstance(suite, dict):
|
|
658
|
+
suite_dict = {}
|
|
659
|
+
for field in ["id", "name", "description", "project_id", "is_baseline",
|
|
660
|
+
"completed_on", "url", "is_master", "is_completed"]:
|
|
661
|
+
if field in suite:
|
|
662
|
+
suite_dict[field] = suite[field]
|
|
663
|
+
suite_dicts.append(suite_dict)
|
|
664
|
+
else:
|
|
665
|
+
suite_dicts.append({"suite": str(suite)})
|
|
666
|
+
return self._to_markup(suite_dicts, output_format)
|
|
667
|
+
except StatusCodeError as e:
|
|
668
|
+
return ToolException(f"Unable to extract test suites: {e}")
|
|
535
669
|
|
|
536
670
|
def _base_loader(self, project_id: str,
|
|
537
671
|
suite_id: Optional[str] = None,
|
|
538
672
|
section_id: Optional[int] = None,
|
|
539
673
|
title_keyword: Optional[str] = None,
|
|
674
|
+
chunking_tool: str = None,
|
|
540
675
|
**kwargs: Any
|
|
541
676
|
) -> Generator[Document, None, None]:
|
|
542
677
|
self._include_attachments = kwargs.get('include_attachments', False)
|
|
543
678
|
self._skip_attachment_extensions = kwargs.get('skip_attachment_extensions', [])
|
|
544
679
|
|
|
545
680
|
try:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
cases = resp.get('cases', [])
|
|
549
|
-
else:
|
|
550
|
-
resp = self._client.cases.get_cases(project_id=project_id)
|
|
551
|
-
cases = resp.get('cases', [])
|
|
681
|
+
# Use unified suite handling method
|
|
682
|
+
cases = self._fetch_cases_with_suite_handling(project_id=project_id, suite_id=suite_id)
|
|
552
683
|
except StatusCodeError as e:
|
|
553
684
|
raise ToolException(f"Unable to extract test cases: {e}")
|
|
554
|
-
|
|
685
|
+
|
|
686
|
+
# Apply filters
|
|
555
687
|
if section_id is not None:
|
|
556
688
|
cases = [case for case in cases if case.get('section_id') == section_id]
|
|
557
689
|
if title_keyword is not None:
|
|
558
690
|
cases = [case for case in cases if title_keyword.lower() in case.get('title', '').lower()]
|
|
559
691
|
|
|
560
692
|
for case in cases:
|
|
561
|
-
|
|
693
|
+
metadata = {
|
|
562
694
|
'project_id': project_id,
|
|
563
695
|
'title': case.get('title', ''),
|
|
564
696
|
'suite_id': suite_id or case.get('suite_id', ''),
|
|
565
697
|
'id': str(case.get('id', '')),
|
|
566
698
|
IndexerKeywords.UPDATED_ON.value: case.get('updated_on') or -1,
|
|
567
699
|
'labels': [lbl['title'] for lbl in case.get('labels', [])],
|
|
568
|
-
'type':
|
|
700
|
+
'type': "testrail_test_case",
|
|
569
701
|
'priority': case.get('priority_id') or -1,
|
|
570
702
|
'milestone': case.get('milestone_id') or -1,
|
|
571
703
|
'estimate': case.get('estimate') or '',
|
|
572
704
|
'automation_type': case.get('custom_automation_type') or -1,
|
|
573
705
|
'section_id': case.get('section_id') or -1,
|
|
574
706
|
'entity_type': 'test_case',
|
|
575
|
-
}
|
|
707
|
+
}
|
|
708
|
+
if chunking_tool:
|
|
709
|
+
# content is in metadata for chunking tool post-processing
|
|
710
|
+
metadata[IndexerKeywords.CONTENT_IN_BYTES.value] = json.dumps(case).encode("utf-8")
|
|
711
|
+
page_content = ""
|
|
712
|
+
else:
|
|
713
|
+
page_content = json.dumps(case)
|
|
714
|
+
yield Document(page_content=page_content, metadata=metadata)
|
|
576
715
|
|
|
577
716
|
def _process_document(self, document: Document) -> Generator[Document, None, None]:
|
|
578
717
|
"""
|
|
@@ -592,37 +731,56 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
592
731
|
return
|
|
593
732
|
|
|
594
733
|
# get base data from the document required to extract attachments and other metadata
|
|
595
|
-
base_data =
|
|
734
|
+
base_data = document.metadata
|
|
596
735
|
case_id = base_data.get("id")
|
|
597
736
|
|
|
598
737
|
# get a list of attachments for the case
|
|
599
|
-
|
|
738
|
+
attachments_response = self._client.attachments.get_attachments_for_case(case_id=case_id)
|
|
739
|
+
|
|
740
|
+
# Extract attachments from response - handle both old and new API response formats
|
|
741
|
+
if isinstance(attachments_response, dict) and 'attachments' in attachments_response:
|
|
742
|
+
attachments = attachments_response['attachments']
|
|
743
|
+
else:
|
|
744
|
+
attachments = attachments_response if isinstance(attachments_response, list) else []
|
|
600
745
|
|
|
601
746
|
# process each attachment to extract its content
|
|
602
747
|
for attachment in attachments:
|
|
603
|
-
|
|
748
|
+
attachment_name = attachment.get('filename') or attachment.get('name')
|
|
749
|
+
attachment['filename'] = attachment_name
|
|
750
|
+
|
|
751
|
+
# Handle filetype: use existing field if present, otherwise extract from filename
|
|
752
|
+
if 'filetype' not in attachment or not attachment['filetype']:
|
|
753
|
+
file_extension = get_file_extension(attachment_name)
|
|
754
|
+
attachment['filetype'] = file_extension.lstrip('.')
|
|
755
|
+
|
|
756
|
+
# Handle is_image: use existing field if present, otherwise check file extension
|
|
757
|
+
if 'is_image' not in attachment:
|
|
758
|
+
file_extension = get_file_extension(attachment_name)
|
|
759
|
+
attachment['is_image'] = file_extension in image_extensions
|
|
760
|
+
|
|
761
|
+
if get_file_extension(attachment_name) in self._skip_attachment_extensions:
|
|
604
762
|
logger.info(f"Skipping attachment {attachment['filename']} with unsupported extension.")
|
|
605
763
|
continue
|
|
606
764
|
|
|
607
765
|
attachment_id = f"attach_{attachment['id']}"
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
# TODO: pass it to chunkers
|
|
611
|
-
yield Document(page_content=self._process_attachment(attachment),
|
|
766
|
+
file_name, content_bytes = self._process_attachment(attachment)
|
|
767
|
+
yield Document(page_content="",
|
|
612
768
|
metadata={
|
|
613
769
|
'project_id': base_data.get('project_id', ''),
|
|
770
|
+
'updated_on': base_data.get('updated_on', ''),
|
|
614
771
|
'id': str(attachment_id),
|
|
615
|
-
IndexerKeywords.PARENT.value: str(case_id),
|
|
616
772
|
'filename': attachment['filename'],
|
|
617
773
|
'filetype': attachment['filetype'],
|
|
618
774
|
'created_on': attachment['created_on'],
|
|
619
775
|
'entity_type': 'test_case_attachment',
|
|
620
776
|
'is_image': attachment['is_image'],
|
|
777
|
+
IndexerKeywords.CONTENT_FILE_NAME.value: file_name,
|
|
778
|
+
IndexerKeywords.CONTENT_IN_BYTES.value: content_bytes
|
|
621
779
|
})
|
|
622
780
|
except json.JSONDecodeError as e:
|
|
623
781
|
raise ToolException(f"Failed to decode JSON from document: {e}")
|
|
624
782
|
|
|
625
|
-
def _process_attachment(self, attachment: Dict[str, Any]) ->
|
|
783
|
+
def _process_attachment(self, attachment: Dict[str, Any]) -> tuple[Any, bytes]:
|
|
626
784
|
"""
|
|
627
785
|
Processes an attachment to extract its content.
|
|
628
786
|
|
|
@@ -633,18 +791,21 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
633
791
|
str: string description of the attachment.
|
|
634
792
|
"""
|
|
635
793
|
|
|
636
|
-
page_content = "This filetype is not supported."
|
|
637
794
|
if attachment['filetype'] == 'txt' :
|
|
638
|
-
|
|
795
|
+
response = self._client.get(endpoint=f"get_attachment/{attachment['id']}")
|
|
796
|
+
if hasattr(response, "content"):
|
|
797
|
+
return attachment['filename'], response.content
|
|
798
|
+
elif isinstance(response, str):
|
|
799
|
+
return attachment['filename'], response.encode("utf-8")
|
|
639
800
|
else:
|
|
640
801
|
try:
|
|
641
802
|
attachment_path = self._client.attachments.get_attachment(attachment_id=attachment['id'], path=f"./{attachment['filename']}")
|
|
642
|
-
|
|
803
|
+
return attachment['filename'], attachment_path.read_bytes()
|
|
643
804
|
except BadRequestError as ai_e:
|
|
644
805
|
logger.error(f"Unable to parse page's content with type: {attachment['filetype']} due to AI service issues: {ai_e}")
|
|
645
806
|
except Exception as e:
|
|
646
807
|
logger.error(f"Unable to parse page's content with type: {attachment['filetype']}: {e}")
|
|
647
|
-
return
|
|
808
|
+
return attachment['filename'], b"This filetype is not supported."
|
|
648
809
|
|
|
649
810
|
def _index_tool_params(self):
|
|
650
811
|
return {
|
|
@@ -658,6 +819,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
658
819
|
'skip_attachment_extensions': (Optional[List[str]], Field(
|
|
659
820
|
description="List of file extensions to skip when processing attachments: i.e. ['.png', '.jpg']",
|
|
660
821
|
default=[])),
|
|
822
|
+
'chunking_tool':(Literal['json', ''], Field(description="Name of chunking tool", default='json'))
|
|
661
823
|
}
|
|
662
824
|
|
|
663
825
|
def _to_markup(self, data: List[Dict], output_format: str) -> str:
|
|
@@ -687,7 +849,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
687
849
|
if output_format == "markdown":
|
|
688
850
|
return df.to_markdown(index=False)
|
|
689
851
|
|
|
690
|
-
@
|
|
852
|
+
@extend_with_parent_available_tools
|
|
691
853
|
def get_available_tools(self):
|
|
692
854
|
tools = [
|
|
693
855
|
{
|
|
@@ -725,6 +887,12 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
|
|
|
725
887
|
"ref": self.update_case,
|
|
726
888
|
"description": self.update_case.__doc__,
|
|
727
889
|
"args_schema": updateCase,
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
"name": "get_suites",
|
|
893
|
+
"ref": self.get_suites,
|
|
894
|
+
"description": self.get_suites.__doc__,
|
|
895
|
+
"args_schema": getSuites,
|
|
728
896
|
}
|
|
729
897
|
]
|
|
730
898
|
return tools
|
|
@@ -7,6 +7,8 @@ import requests
|
|
|
7
7
|
from pydantic import create_model, Field
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
# DEPRECATED: Tool names no longer use prefixes
|
|
11
|
+
# Kept for backward compatibility only
|
|
10
12
|
TOOLKIT_SPLITTER = "___"
|
|
11
13
|
TOOL_NAME_LIMIT = 64
|
|
12
14
|
|
|
@@ -22,10 +24,13 @@ def clean_string(s: str, max_length: int = 0):
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def get_max_toolkit_length(selected_tools: Any):
|
|
25
|
-
"""Calculates the maximum length of the toolkit name
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
"""DEPRECATED: Calculates the maximum length of the toolkit name.
|
|
28
|
+
|
|
29
|
+
This function is deprecated as tool names no longer use prefixes.
|
|
30
|
+
Returns a fixed value for backward compatibility.
|
|
31
|
+
"""
|
|
32
|
+
# Return a reasonable default since we no longer use prefixes
|
|
33
|
+
return 50
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
def parse_list(list_str: str = None) -> List[str]:
|
|
@@ -55,6 +60,8 @@ def parse_type(type_str):
|
|
|
55
60
|
"""Parse a type string into an actual Python type."""
|
|
56
61
|
try:
|
|
57
62
|
# Evaluate the type string using builtins and imported modules
|
|
63
|
+
if type_str == 'number':
|
|
64
|
+
type_str = 'int'
|
|
58
65
|
return eval(type_str, {**vars(builtins), **globals()})
|
|
59
66
|
except Exception as e:
|
|
60
67
|
print(f"Error parsing type: {e}")
|
|
@@ -95,3 +102,20 @@ def check_connection_response(check_fun):
|
|
|
95
102
|
else:
|
|
96
103
|
return f"Service Unreachable: return code {response.status_code}"
|
|
97
104
|
return _wrapper
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def make_json_serializable(obj):
|
|
108
|
+
if isinstance(obj, BaseModel):
|
|
109
|
+
return obj.model_dump()
|
|
110
|
+
if isinstance(obj, dict):
|
|
111
|
+
return {k: make_json_serializable(v) for k, v in obj.items()}
|
|
112
|
+
if isinstance(obj, list):
|
|
113
|
+
return [make_json_serializable(i) for i in obj]
|
|
114
|
+
if isinstance(obj, bool):
|
|
115
|
+
return bool(obj)
|
|
116
|
+
if isinstance(obj, (str, int, float)) or obj is None:
|
|
117
|
+
return obj
|
|
118
|
+
# Fallback: handle objects that look like booleans but were not caught above
|
|
119
|
+
if str(obj) in ("True", "False"):
|
|
120
|
+
return str(obj) == "True"
|
|
121
|
+
return str(obj)
|