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.
- 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 +3601 -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 +1256 -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 +64 -8
- alita_sdk/community/inventory/__init__.py +224 -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/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +10 -0
- alita_sdk/configurations/ado.py +4 -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 +96 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +0 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +79 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +1 -3
- alita_sdk/configurations/report_portal.py +19 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +19 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +18 -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 +12 -2
- alita_sdk/runtime/clients/client.py +235 -66
- 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 +373 -0
- alita_sdk/runtime/langchain/assistant.py +123 -17
- alita_sdk/runtime/langchain/constants.py +8 -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/AlitaJSONLoader.py +8 -2
- 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 +187 -40
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +406 -91
- alita_sdk/runtime/langchain/utils.py +51 -8
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/toolkits/__init__.py +26 -0
- alita_sdk/runtime/toolkits/application.py +9 -2
- alita_sdk/runtime/toolkits/artifact.py +19 -7
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +780 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +214 -60
- alita_sdk/runtime/toolkits/vectorstore.py +9 -4
- alita_sdk/runtime/tools/__init__.py +22 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +312 -19
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +212 -0
- alita_sdk/runtime/tools/llm.py +539 -180
- 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/vectorstore.py +62 -63
- alita_sdk/runtime/tools/vectorstore_base.py +156 -85
- alita_sdk/runtime/utils/AlitaCallback.py +106 -20
- alita_sdk/runtime/utils/mcp_client.py +465 -0
- alita_sdk/runtime/utils/mcp_oauth.py +244 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +405 -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 +14 -0
- alita_sdk/tools/__init__.py +78 -35
- alita_sdk/tools/ado/__init__.py +0 -1
- alita_sdk/tools/ado/repos/__init__.py +10 -6
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -11
- alita_sdk/tools/ado/test_plan/__init__.py +10 -7
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -23
- alita_sdk/tools/ado/wiki/__init__.py +10 -11
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -28
- alita_sdk/tools/ado/work_item/__init__.py +10 -11
- alita_sdk/tools/ado/work_item/ado_wrapper.py +63 -10
- alita_sdk/tools/advanced_jira_mining/__init__.py +10 -7
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -11
- alita_sdk/tools/azure_ai/search/__init__.py +11 -7
- alita_sdk/tools/base_indexer_toolkit.py +392 -86
- alita_sdk/tools/bitbucket/__init__.py +18 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +52 -9
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +40 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +17 -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 +1 -0
- 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 +9 -6
- alita_sdk/tools/cloud/azure/__init__.py +9 -6
- alita_sdk/tools/cloud/gcp/__init__.py +9 -6
- alita_sdk/tools/cloud/k8s/__init__.py +9 -6
- alita_sdk/tools/code/linter/__init__.py +7 -7
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +18 -12
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +14 -11
- alita_sdk/tools/confluence/api_wrapper.py +198 -58
- alita_sdk/tools/confluence/loader.py +10 -0
- alita_sdk/tools/custom_open_api/__init__.py +9 -4
- alita_sdk/tools/elastic/__init__.py +8 -7
- alita_sdk/tools/elitea_base.py +543 -64
- alita_sdk/tools/figma/__init__.py +10 -8
- alita_sdk/tools/figma/api_wrapper.py +352 -153
- alita_sdk/tools/github/__init__.py +13 -11
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +75 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/gitlab/__init__.py +11 -10
- alita_sdk/tools/gitlab/api_wrapper.py +135 -45
- alita_sdk/tools/gitlab_org/__init__.py +11 -9
- alita_sdk/tools/google/bigquery/__init__.py +12 -13
- alita_sdk/tools/google_places/__init__.py +18 -10
- alita_sdk/tools/jira/__init__.py +14 -8
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +8 -7
- alita_sdk/tools/localgit/local_git.py +56 -54
- alita_sdk/tools/memory/__init__.py +27 -11
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +8 -7
- alita_sdk/tools/openapi/__init__.py +10 -1
- alita_sdk/tools/pandas/__init__.py +8 -7
- alita_sdk/tools/pandas/api_wrapper.py +7 -25
- alita_sdk/tools/postman/__init__.py +8 -10
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +8 -9
- alita_sdk/tools/qtest/__init__.py +19 -13
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/report_portal/__init__.py +20 -15
- alita_sdk/tools/salesforce/__init__.py +19 -15
- alita_sdk/tools/servicenow/__init__.py +14 -11
- alita_sdk/tools/sharepoint/__init__.py +14 -13
- 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 +10 -7
- alita_sdk/tools/sql/__init__.py +19 -18
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +18 -12
- alita_sdk/tools/testrail/__init__.py +10 -10
- alita_sdk/tools/testrail/api_wrapper.py +213 -45
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +181 -61
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +12 -7
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/zephyr/__init__.py +9 -6
- alita_sdk/tools/zephyr_enterprise/__init__.py +13 -8
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +17 -7
- alita_sdk/tools/zephyr_essential/__init__.py +13 -9
- alita_sdk/tools/zephyr_essential/api_wrapper.py +289 -47
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +10 -7
- alita_sdk/tools/zephyr_scale/api_wrapper.py +6 -2
- alita_sdk/tools/zephyr_squad/__init__.py +9 -6
- {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/METADATA +180 -33
- alita_sdk-0.3.499.dist-info/RECORD +433 -0
- alita_sdk-0.3.499.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.263.dist-info/RECORD +0 -342
- {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.263.dist-info → alita_sdk-0.3.499.dist-info}/licenses/LICENSE +0 -0
- {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 ...
|
|
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(
|
|
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
|
-
|
|
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
|
-
]
|
|
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,
|
|
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=
|
|
106
|
-
description=
|
|
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 ...
|
|
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(
|
|
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
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
'
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
390
|
-
"
|
|
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
|
-
@
|
|
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,
|
|
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=
|
|
103
|
-
description=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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""
|
|
170
|
+
page_content = page_content.replace(f"", 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
|
-
|
|
231
|
-
'
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
@
|
|
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,
|
|
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=
|
|
104
|
-
description=
|
|
102
|
+
name=tool["name"],
|
|
103
|
+
description=description,
|
|
105
104
|
args_schema=tool["args_schema"]
|
|
106
105
|
))
|
|
107
106
|
return cls(tools=tools)
|