alita-sdk 0.3.205__py3-none-any.whl → 0.3.207__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/runtime/clients/client.py +314 -11
- alita_sdk/runtime/langchain/assistant.py +22 -21
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +1 -4
- alita_sdk/runtime/langchain/langraph_agent.py +6 -1
- alita_sdk/runtime/langchain/store_manager.py +4 -4
- alita_sdk/runtime/toolkits/application.py +5 -10
- alita_sdk/runtime/toolkits/tools.py +11 -21
- alita_sdk/runtime/tools/vectorstore.py +25 -11
- alita_sdk/runtime/utils/streamlit.py +505 -222
- alita_sdk/runtime/utils/toolkit_runtime.py +147 -0
- alita_sdk/runtime/utils/toolkit_utils.py +157 -0
- alita_sdk/runtime/utils/utils.py +5 -0
- alita_sdk/tools/__init__.py +2 -0
- alita_sdk/tools/ado/repos/repos_wrapper.py +20 -13
- alita_sdk/tools/bitbucket/api_wrapper.py +5 -5
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +54 -29
- alita_sdk/tools/elitea_base.py +9 -4
- alita_sdk/tools/gitlab/__init__.py +22 -10
- alita_sdk/tools/gitlab/api_wrapper.py +278 -253
- alita_sdk/tools/gitlab/tools.py +354 -376
- alita_sdk/tools/llm/llm_utils.py +0 -6
- alita_sdk/tools/memory/__init__.py +54 -10
- alita_sdk/tools/openapi/__init__.py +14 -3
- alita_sdk/tools/sharepoint/__init__.py +2 -1
- alita_sdk/tools/sharepoint/api_wrapper.py +11 -3
- alita_sdk/tools/testrail/api_wrapper.py +39 -16
- alita_sdk/tools/utils/content_parser.py +77 -13
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/RECORD +32 -40
- alita_sdk/community/analysis/__init__.py +0 -0
- alita_sdk/community/analysis/ado_analyse/__init__.py +0 -103
- alita_sdk/community/analysis/ado_analyse/api_wrapper.py +0 -261
- alita_sdk/community/analysis/github_analyse/__init__.py +0 -98
- alita_sdk/community/analysis/github_analyse/api_wrapper.py +0 -166
- alita_sdk/community/analysis/gitlab_analyse/__init__.py +0 -110
- alita_sdk/community/analysis/gitlab_analyse/api_wrapper.py +0 -172
- alita_sdk/community/analysis/jira_analyse/__init__.py +0 -141
- alita_sdk/community/analysis/jira_analyse/api_wrapper.py +0 -252
- alita_sdk/runtime/llms/alita.py +0 -259
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
"""
|
2
|
+
Toolkit runtime utilities for event dispatching and execution context.
|
3
|
+
This module provides tools with the ability to dispatch custom events during execution.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import logging
|
8
|
+
from typing import Dict, Any, Optional
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
def dispatch_custom_event(event_type: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
14
|
+
"""
|
15
|
+
Dispatch a custom event from within a toolkit tool execution.
|
16
|
+
|
17
|
+
This function can be called by toolkit tools to send events back to the runtime
|
18
|
+
for monitoring, logging, or other purposes.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
event_type: Type of the event (e.g., "progress", "warning", "info")
|
22
|
+
data: Event data dictionary
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Event dictionary if successful, None if no executor context available
|
26
|
+
|
27
|
+
Example:
|
28
|
+
```python
|
29
|
+
from alita_sdk.runtime.utils.toolkit_runtime import dispatch_custom_event
|
30
|
+
|
31
|
+
def my_tool_function(param1, param2):
|
32
|
+
# Dispatch a progress event
|
33
|
+
dispatch_custom_event("progress", {
|
34
|
+
"message": "Processing started",
|
35
|
+
"step": 1,
|
36
|
+
"total_steps": 3
|
37
|
+
})
|
38
|
+
|
39
|
+
# Do some work
|
40
|
+
result = process_data(param1, param2)
|
41
|
+
|
42
|
+
# Dispatch completion event
|
43
|
+
dispatch_custom_event("completion", {
|
44
|
+
"message": "Processing completed",
|
45
|
+
"result_size": len(result)
|
46
|
+
})
|
47
|
+
|
48
|
+
return result
|
49
|
+
```
|
50
|
+
"""
|
51
|
+
try:
|
52
|
+
# Try to get the current executor context
|
53
|
+
if hasattr(sys.modules[__name__], 'toolkit_dispatch_context'):
|
54
|
+
context = sys.modules[__name__].toolkit_dispatch_context
|
55
|
+
return context.dispatch_custom_event(event_type, data)
|
56
|
+
else:
|
57
|
+
# No executor context available - this is normal when not in test mode
|
58
|
+
logger.debug(f"No toolkit executor context available for event: {event_type}")
|
59
|
+
return None
|
60
|
+
except Exception as e:
|
61
|
+
logger.warning(f"Error dispatching custom event {event_type}: {e}")
|
62
|
+
return None
|
63
|
+
|
64
|
+
|
65
|
+
def get_executor_context():
|
66
|
+
"""
|
67
|
+
Get the current toolkit executor context if available.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
ToolkitExecutor context or None if not in execution context
|
71
|
+
"""
|
72
|
+
try:
|
73
|
+
if hasattr(sys.modules[__name__], 'toolkit_dispatch_context'):
|
74
|
+
return sys.modules[__name__].toolkit_dispatch_context.executor
|
75
|
+
return None
|
76
|
+
except Exception:
|
77
|
+
return None
|
78
|
+
|
79
|
+
|
80
|
+
def is_in_test_mode() -> bool:
|
81
|
+
"""
|
82
|
+
Check if the toolkit is currently running in test mode.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
True if running in test mode with executor context, False otherwise
|
86
|
+
"""
|
87
|
+
return get_executor_context() is not None
|
88
|
+
|
89
|
+
|
90
|
+
class ToolkitRuntimeContext:
|
91
|
+
"""
|
92
|
+
Context manager for toolkit runtime execution.
|
93
|
+
|
94
|
+
This can be used by tools that need to perform setup/cleanup operations
|
95
|
+
when running in test mode vs normal execution.
|
96
|
+
"""
|
97
|
+
|
98
|
+
def __init__(self, tool_name: str):
|
99
|
+
self.tool_name = tool_name
|
100
|
+
self.executor = get_executor_context()
|
101
|
+
|
102
|
+
def __enter__(self):
|
103
|
+
if self.executor:
|
104
|
+
dispatch_custom_event("tool_start", {
|
105
|
+
"tool_name": self.tool_name,
|
106
|
+
"message": f"Starting execution of {self.tool_name}"
|
107
|
+
})
|
108
|
+
return self
|
109
|
+
|
110
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
111
|
+
if self.executor:
|
112
|
+
if exc_type is None:
|
113
|
+
dispatch_custom_event("tool_end", {
|
114
|
+
"tool_name": self.tool_name,
|
115
|
+
"message": f"Completed execution of {self.tool_name}",
|
116
|
+
"success": True
|
117
|
+
})
|
118
|
+
else:
|
119
|
+
dispatch_custom_event("tool_error", {
|
120
|
+
"tool_name": self.tool_name,
|
121
|
+
"message": f"Error in {self.tool_name}: {exc_val}",
|
122
|
+
"success": False,
|
123
|
+
"error_type": exc_type.__name__ if exc_type else "Unknown"
|
124
|
+
})
|
125
|
+
return False # Don't suppress exceptions
|
126
|
+
|
127
|
+
def dispatch_progress(self, message: str, step: int = None, total_steps: int = None, **kwargs):
|
128
|
+
"""Convenience method for dispatching progress events."""
|
129
|
+
data = {"message": message, "tool_name": self.tool_name}
|
130
|
+
if step is not None:
|
131
|
+
data["step"] = step
|
132
|
+
if total_steps is not None:
|
133
|
+
data["total_steps"] = total_steps
|
134
|
+
data.update(kwargs)
|
135
|
+
dispatch_custom_event("progress", data)
|
136
|
+
|
137
|
+
def dispatch_info(self, message: str, **kwargs):
|
138
|
+
"""Convenience method for dispatching info events."""
|
139
|
+
data = {"message": message, "tool_name": self.tool_name}
|
140
|
+
data.update(kwargs)
|
141
|
+
dispatch_custom_event("info", data)
|
142
|
+
|
143
|
+
def dispatch_warning(self, message: str, **kwargs):
|
144
|
+
"""Convenience method for dispatching warning events."""
|
145
|
+
data = {"message": message, "tool_name": self.tool_name}
|
146
|
+
data.update(kwargs)
|
147
|
+
dispatch_custom_event("warning", data)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"""
|
2
|
+
Toolkit utilities for instantiating and managing toolkits.
|
3
|
+
This module provides toolkit management functions that are not tied to any specific interface.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import logging
|
7
|
+
import random
|
8
|
+
from typing import Dict, Any, Optional, List
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
|
14
|
+
llm_client: Any,
|
15
|
+
alita_client: Optional[Any] = None) -> List[Any]:
|
16
|
+
"""
|
17
|
+
Instantiate a toolkit with LLM client support.
|
18
|
+
|
19
|
+
This is a variant of instantiate_toolkit that includes LLM client support
|
20
|
+
for toolkits that require LLM capabilities.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
toolkit_config: Configuration dictionary for the toolkit
|
24
|
+
llm_client: LLM client instance for tools that need LLM capabilities
|
25
|
+
client: Optional additional client instance
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
List of instantiated tools from the toolkit
|
29
|
+
|
30
|
+
Raises:
|
31
|
+
ValueError: If required configuration or client is missing
|
32
|
+
Exception: If toolkit instantiation fails
|
33
|
+
"""
|
34
|
+
try:
|
35
|
+
from ..toolkits.tools import get_tools
|
36
|
+
|
37
|
+
toolkit_name = toolkit_config.get('toolkit_name')
|
38
|
+
if not toolkit_name:
|
39
|
+
raise ValueError("toolkit_name is required in configuration")
|
40
|
+
|
41
|
+
if not llm_client:
|
42
|
+
raise ValueError("LLM client is required but not provided")
|
43
|
+
|
44
|
+
settings = toolkit_config.get('settings', {})
|
45
|
+
|
46
|
+
# Log the configuration being used
|
47
|
+
logger.info(f"Instantiating toolkit {toolkit_name} with LLM client")
|
48
|
+
logger.debug(f"Toolkit {toolkit_name} configuration: {toolkit_config}")
|
49
|
+
|
50
|
+
# Create a tool configuration dict with required fields
|
51
|
+
tool_config = {
|
52
|
+
'id': toolkit_config.get('id', random.randint(1, 1000000)),
|
53
|
+
'type': toolkit_config.get('type', toolkit_name.lower()),
|
54
|
+
'settings': settings,
|
55
|
+
'toolkit_name': toolkit_name
|
56
|
+
}
|
57
|
+
|
58
|
+
# Get tools using the toolkit configuration with clients
|
59
|
+
# Parameter order: get_tools(tools_list, alita_client, llm, memory_store)
|
60
|
+
tools = get_tools([tool_config], alita_client, llm_client)
|
61
|
+
|
62
|
+
if not tools:
|
63
|
+
logger.warning(f"No tools returned for toolkit {toolkit_name}")
|
64
|
+
return []
|
65
|
+
|
66
|
+
logger.info(f"Successfully instantiated toolkit {toolkit_name} with {len(tools)} tools")
|
67
|
+
return tools
|
68
|
+
|
69
|
+
except Exception as e:
|
70
|
+
logger.error(f"Error instantiating toolkit {toolkit_name} with client: {str(e)}")
|
71
|
+
raise
|
72
|
+
|
73
|
+
|
74
|
+
def get_toolkit_tools(toolkit_instance: Any) -> List[Any]:
|
75
|
+
"""
|
76
|
+
Extract tools from an instantiated toolkit instance.
|
77
|
+
|
78
|
+
This function provides a standardized way to get tools from various
|
79
|
+
toolkit implementations that might have different interfaces.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
toolkit_instance: An instantiated toolkit object
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
List of tools from the toolkit
|
86
|
+
|
87
|
+
Raises:
|
88
|
+
ValueError: If no tools can be extracted from the toolkit
|
89
|
+
"""
|
90
|
+
try:
|
91
|
+
# Try different methods to get tools from the toolkit
|
92
|
+
if hasattr(toolkit_instance, 'get_tools'):
|
93
|
+
tools = toolkit_instance.get_tools()
|
94
|
+
elif hasattr(toolkit_instance, 'tools'):
|
95
|
+
tools = toolkit_instance.tools
|
96
|
+
elif hasattr(toolkit_instance, '_tools'):
|
97
|
+
tools = toolkit_instance._tools
|
98
|
+
else:
|
99
|
+
raise ValueError("Could not find tools in the toolkit instance")
|
100
|
+
|
101
|
+
if not tools:
|
102
|
+
logger.warning("Toolkit instance returned empty tools list")
|
103
|
+
return []
|
104
|
+
|
105
|
+
logger.info(f"Extracted {len(tools)} tools from toolkit instance")
|
106
|
+
return tools
|
107
|
+
|
108
|
+
except Exception as e:
|
109
|
+
logger.error(f"Error extracting tools from toolkit: {str(e)}")
|
110
|
+
raise
|
111
|
+
|
112
|
+
|
113
|
+
def find_tool_by_name(tools: List[Any], tool_name: str) -> Optional[Any]:
|
114
|
+
"""
|
115
|
+
Find a specific tool by name from a list of tools.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
tools: List of tool instances
|
119
|
+
tool_name: Name of the tool to find
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
The tool instance if found, None otherwise
|
123
|
+
"""
|
124
|
+
for tool in tools:
|
125
|
+
# Check various attributes that might contain the tool name
|
126
|
+
if hasattr(tool, 'name') and tool.name == tool_name:
|
127
|
+
return tool
|
128
|
+
elif hasattr(tool, 'func') and hasattr(tool.func, '__name__') and tool.func.__name__ == tool_name:
|
129
|
+
return tool
|
130
|
+
elif hasattr(tool, '__name__') and tool.__name__ == tool_name:
|
131
|
+
return tool
|
132
|
+
|
133
|
+
return None
|
134
|
+
|
135
|
+
|
136
|
+
def get_tool_names(tools: List[Any]) -> List[str]:
|
137
|
+
"""
|
138
|
+
Extract tool names from a list of tools.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
tools: List of tool instances
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
List of tool names
|
145
|
+
"""
|
146
|
+
tool_names = []
|
147
|
+
for tool in tools:
|
148
|
+
if hasattr(tool, 'name'):
|
149
|
+
tool_names.append(tool.name)
|
150
|
+
elif hasattr(tool, 'func') and hasattr(tool.func, '__name__'):
|
151
|
+
tool_names.append(tool.func.__name__)
|
152
|
+
elif hasattr(tool, '__name__'):
|
153
|
+
tool_names.append(tool.__name__)
|
154
|
+
else:
|
155
|
+
tool_names.append(str(tool))
|
156
|
+
|
157
|
+
return tool_names
|
alita_sdk/runtime/utils/utils.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
import re
|
2
|
+
from enum import Enum
|
2
3
|
|
3
4
|
TOOLKIT_SPLITTER = "___"
|
4
5
|
|
6
|
+
class IndexerKeywords(Enum):
|
7
|
+
DEPENDENT_DOCS = 'dependent_docs'
|
8
|
+
PARENT = 'parent_id'
|
9
|
+
|
5
10
|
# This pattern matches characters that are NOT alphanumeric, underscores, or hyphens
|
6
11
|
clean_string_pattern = re.compile(r'[^a-zA-Z0-9_.-]')
|
7
12
|
|
alita_sdk/tools/__init__.py
CHANGED
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
from importlib import import_module
|
3
3
|
from typing import Optional
|
4
4
|
|
5
|
+
from langchain_core.tools import ToolException
|
5
6
|
from langgraph.store.base import BaseStore
|
6
7
|
|
7
8
|
logger = logging.getLogger(__name__)
|
@@ -113,6 +114,7 @@ def get_tools(tools_list, alita, llm, store: Optional[BaseStore] = None, *args,
|
|
113
114
|
|
114
115
|
except Exception as e:
|
115
116
|
logger.error(f"Error getting tools for {tool_type}: {e}")
|
117
|
+
raise ToolException(f"Error getting tools for {tool_type}: {e}")
|
116
118
|
|
117
119
|
# Handle ADO repos special case (it might be requested as azure_devops_repos)
|
118
120
|
elif tool_type in ['ado_repos', 'azure_devops_repos'] and 'ado_repos' in AVAILABLE_TOOLS:
|
@@ -250,6 +250,7 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
|
|
250
250
|
token: Optional[SecretStr]
|
251
251
|
_client: Optional[GitClient] = PrivateAttr()
|
252
252
|
|
253
|
+
llm: Optional[Any] = None
|
253
254
|
# Vector store configuration
|
254
255
|
connection_string: Optional[SecretStr] = None
|
255
256
|
collection_name: Optional[str] = None
|
@@ -303,24 +304,30 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
|
|
303
304
|
|
304
305
|
def _get_files(
|
305
306
|
self,
|
306
|
-
|
307
|
-
|
307
|
+
path: str = "",
|
308
|
+
branch: str = None,
|
308
309
|
recursion_level: str = "Full",
|
309
310
|
) -> str:
|
311
|
+
"""Get list of files from a repository path and branch.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
path (str): Path within the repository to list files from
|
315
|
+
branch (str): Branch to get files from. Defaults to base_branch if None.
|
316
|
+
recursion_level (str): OneLevel - includes immediate children, Full - includes all items, None - no recursion
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
List[str]: List of file paths
|
310
320
|
"""
|
311
|
-
|
312
|
-
recursion_level: OneLevel - includes immediate children, Full - includes all items, None - no recursion
|
313
|
-
"""
|
314
|
-
branch_name = branch_name if branch_name else self.base_branch
|
321
|
+
branch = branch if branch else self.base_branch
|
315
322
|
files: List[str] = []
|
316
323
|
try:
|
317
324
|
version_descriptor = GitVersionDescriptor(
|
318
|
-
version=
|
325
|
+
version=branch, version_type="branch"
|
319
326
|
)
|
320
327
|
items = self._client.get_items(
|
321
328
|
repository_id=self.repository_id,
|
322
329
|
project=self.project,
|
323
|
-
scope_path=
|
330
|
+
scope_path=path,
|
324
331
|
recursion_level=recursion_level,
|
325
332
|
version_descriptor=version_descriptor,
|
326
333
|
include_content_metadata=True,
|
@@ -334,7 +341,7 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
|
|
334
341
|
item = items.pop(0)
|
335
342
|
if item.git_object_type == "blob":
|
336
343
|
files.append(item.path)
|
337
|
-
return str
|
344
|
+
return files # Changed to return list directly instead of str
|
338
345
|
|
339
346
|
def set_active_branch(self, branch_name: str) -> str:
|
340
347
|
"""
|
@@ -389,7 +396,7 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
|
|
389
396
|
logger.error(msg)
|
390
397
|
return ToolException(msg)
|
391
398
|
|
392
|
-
def list_files(self, directory_path: str = "", branch_name: str = None) -> str:
|
399
|
+
def list_files(self, directory_path: str = "", branch_name: str = None) -> List[str]:
|
393
400
|
"""
|
394
401
|
Recursively fetches files from a directory in the repo.
|
395
402
|
|
@@ -398,12 +405,12 @@ class ReposApiWrapper(BaseCodeToolApiWrapper):
|
|
398
405
|
branch_name (str): The name of the branch where the files to be received.
|
399
406
|
|
400
407
|
Returns:
|
401
|
-
str: List of file paths, or an error message.
|
408
|
+
List[str]: List of file paths, or an error message.
|
402
409
|
"""
|
403
410
|
self.active_branch = branch_name if branch_name else self.active_branch
|
404
411
|
return self._get_files(
|
405
|
-
|
406
|
-
|
412
|
+
path=directory_path,
|
413
|
+
branch=self.active_branch if self.active_branch else self.base_branch,
|
407
414
|
)
|
408
415
|
|
409
416
|
def parse_pull_request_comments(
|
@@ -5,7 +5,7 @@ import logging
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
6
6
|
|
7
7
|
from langchain_core.tools import ToolException
|
8
|
-
from pydantic import
|
8
|
+
from pydantic import model_validator, SecretStr
|
9
9
|
from .bitbucket_constants import create_pr_data
|
10
10
|
from .cloud_api_wrapper import BitbucketCloudApi, BitbucketServerApi
|
11
11
|
from pydantic.fields import PrivateAttr
|
@@ -172,26 +172,26 @@ class BitbucketAPIWrapper(BaseCodeToolApiWrapper):
|
|
172
172
|
"""
|
173
173
|
return self._bitbucket.get_pull_requests()
|
174
174
|
|
175
|
-
def get_pull_request(self, pr_id: str) -> Any:
|
175
|
+
def get_pull_request(self, pr_id: str) -> Dict[str, Any]:
|
176
176
|
"""
|
177
177
|
Get details of a pull request
|
178
178
|
Parameters:
|
179
179
|
pr_id(str): the pull request ID
|
180
180
|
Returns:
|
181
|
-
|
181
|
+
dict: Details of the pull request as a dictionary
|
182
182
|
"""
|
183
183
|
try:
|
184
184
|
return self._bitbucket.get_pull_request(pr_id=pr_id)
|
185
185
|
except Exception as e:
|
186
186
|
return ToolException(f"Can't get pull request `{pr_id}` due to error:\n{str(e)}")
|
187
187
|
|
188
|
-
def get_pull_requests_changes(self, pr_id: str) -> Any:
|
188
|
+
def get_pull_requests_changes(self, pr_id: str) -> Dict[str, Any]:
|
189
189
|
"""
|
190
190
|
Get changes of a pull request
|
191
191
|
Parameters:
|
192
192
|
pr_id(str): the pull request ID
|
193
193
|
Returns:
|
194
|
-
|
194
|
+
dict: Changes of the pull request as a dictionary
|
195
195
|
"""
|
196
196
|
try:
|
197
197
|
return self._bitbucket.get_pull_requests_changes(pr_id=pr_id)
|
@@ -17,6 +17,21 @@ if TYPE_CHECKING:
|
|
17
17
|
pass
|
18
18
|
|
19
19
|
|
20
|
+
def normalize_response(response) -> Dict[str, Any]:
|
21
|
+
"""
|
22
|
+
Normalize API response to dictionary format.
|
23
|
+
Handles different response types from Bitbucket APIs.
|
24
|
+
"""
|
25
|
+
if isinstance(response, dict):
|
26
|
+
return response
|
27
|
+
if hasattr(response, 'to_dict'):
|
28
|
+
return response.to_dict()
|
29
|
+
if hasattr(response, '__dict__'):
|
30
|
+
return {k: v for k, v in response.__dict__.items()
|
31
|
+
if isinstance(v, (str, int, float, bool, list, dict, type(None)))}
|
32
|
+
return {"raw_response": str(response)}
|
33
|
+
|
34
|
+
|
20
35
|
class BitbucketApiAbstract(ABC):
|
21
36
|
|
22
37
|
@abstractmethod
|
@@ -48,11 +63,11 @@ class BitbucketApiAbstract(ABC):
|
|
48
63
|
pass
|
49
64
|
|
50
65
|
@abstractmethod
|
51
|
-
def get_pull_request(self, pr_id: str) -> Any:
|
66
|
+
def get_pull_request(self, pr_id: str) -> Dict[str, Any]:
|
52
67
|
pass
|
53
68
|
|
54
69
|
@abstractmethod
|
55
|
-
def get_pull_requests_changes(self, pr_id: str) -> Any:
|
70
|
+
def get_pull_requests_changes(self, pr_id: str) -> Dict[str, Any]:
|
56
71
|
pass
|
57
72
|
|
58
73
|
@abstractmethod
|
@@ -95,7 +110,7 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
95
110
|
|
96
111
|
def get_files_list(self, file_path: str, branch: str) -> list:
|
97
112
|
files = self.api_client.get_file_list(project_key=self.project, repository_slug=self.repository, query=branch,
|
98
|
-
|
113
|
+
sub_folder=file_path)
|
99
114
|
files_list = []
|
100
115
|
for file in files:
|
101
116
|
files_list.append(file['path'])
|
@@ -112,7 +127,7 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
112
127
|
)
|
113
128
|
|
114
129
|
def update_file(self, file_path: str, update_query: str, branch: str) -> str:
|
115
|
-
file_content = self.get_file(file_path
|
130
|
+
file_content = self.get_file(file_path=file_path, branch=branch)
|
116
131
|
updated_file_content = file_content
|
117
132
|
for old, new in extract_old_new_pairs(update_query):
|
118
133
|
if not old.strip():
|
@@ -126,8 +141,8 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
126
141
|
"the current file contents."
|
127
142
|
)
|
128
143
|
|
129
|
-
|
130
|
-
|
144
|
+
source_commit_generator = self.api_client.get_commits(project_key=self.project, repository_slug=self.repository,
|
145
|
+
hash_newest=branch, limit=1)
|
131
146
|
source_commit = next(source_commit_generator)
|
132
147
|
return self.api_client.update_file(
|
133
148
|
project_key=self.project,
|
@@ -138,7 +153,7 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
138
153
|
filename=file_path,
|
139
154
|
source_commit_id=source_commit['id']
|
140
155
|
)
|
141
|
-
|
156
|
+
|
142
157
|
def get_pull_request_commits(self, pr_id: str) -> List[Dict[str, Any]]:
|
143
158
|
"""
|
144
159
|
Get commits from a pull request
|
@@ -147,27 +162,32 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
147
162
|
Returns:
|
148
163
|
List[Dict[str, Any]]: List of commits in the pull request
|
149
164
|
"""
|
150
|
-
return self.api_client.get_pull_requests_commits(project_key=self.project, repository_slug=self.repository,
|
151
|
-
|
165
|
+
return self.api_client.get_pull_requests_commits(project_key=self.project, repository_slug=self.repository,
|
166
|
+
pull_request_id=pr_id)
|
167
|
+
|
152
168
|
def get_pull_request(self, pr_id):
|
153
169
|
""" Get details of a pull request
|
154
170
|
Parameters:
|
155
171
|
pr_id(str): the pull request ID
|
156
172
|
Returns:
|
157
|
-
Any: Details of the pull request
|
173
|
+
Dict[str, Any]: Details of the pull request
|
158
174
|
"""
|
159
|
-
|
160
|
-
|
161
|
-
|
175
|
+
response = self.api_client.get_pull_request(project_key=self.project, repository_slug=self.repository,
|
176
|
+
pull_request_id=pr_id)
|
177
|
+
return normalize_response(response)
|
178
|
+
|
179
|
+
def get_pull_requests_changes(self, pr_id: str) -> Dict[str, Any]:
|
162
180
|
"""
|
163
181
|
Get changes of a pull request
|
164
182
|
Parameters:
|
165
183
|
pr_id(str): the pull request ID
|
166
184
|
Returns:
|
167
|
-
Any: Changes of the pull request
|
185
|
+
Dict[str, Any]: Changes of the pull request
|
168
186
|
"""
|
169
|
-
|
170
|
-
|
187
|
+
response = self.api_client.get_pull_requests_changes(project_key=self.project, repository_slug=self.repository,
|
188
|
+
pull_request_id=pr_id)
|
189
|
+
return normalize_response(response)
|
190
|
+
|
171
191
|
def add_pull_request_comment(self, pr_id, content, inline=None):
|
172
192
|
"""
|
173
193
|
Add a comment to a pull request. Supports multiple content types and inline comments.
|
@@ -204,6 +224,7 @@ class BitbucketServerApi(BitbucketApiAbstract):
|
|
204
224
|
**comment_data
|
205
225
|
)
|
206
226
|
|
227
|
+
|
207
228
|
class BitbucketCloudApi(BitbucketApiAbstract):
|
208
229
|
api_client: Cloud
|
209
230
|
|
@@ -249,8 +270,9 @@ class BitbucketCloudApi(BitbucketApiAbstract):
|
|
249
270
|
index = 0
|
250
271
|
# TODO: add pagination
|
251
272
|
for item in \
|
252
|
-
|
253
|
-
|
273
|
+
self.repository.get(
|
274
|
+
path=f'src/{branch}/{file_path}?max_depth=100&pagelen=100&fields=values.path&q=type="commit_file"')[
|
275
|
+
'values']:
|
254
276
|
files_list.append(item['path'])
|
255
277
|
return files_list
|
256
278
|
|
@@ -259,7 +281,8 @@ class BitbucketCloudApi(BitbucketApiAbstract):
|
|
259
281
|
'branch': f'{branch}',
|
260
282
|
f'{file_path}': f'{file_contents}',
|
261
283
|
}
|
262
|
-
return self.repository.post(path='src', data=form_data, files={},
|
284
|
+
return self.repository.post(path='src', data=form_data, files={},
|
285
|
+
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
263
286
|
|
264
287
|
def update_file(self, file_path: str, update_query: str, branch: str) -> ToolException | str:
|
265
288
|
|
@@ -277,7 +300,7 @@ class BitbucketCloudApi(BitbucketApiAbstract):
|
|
277
300
|
"the current file contents."
|
278
301
|
)
|
279
302
|
return self.create_file(file_path, updated_file_content, branch)
|
280
|
-
|
303
|
+
|
281
304
|
def get_pull_request_commits(self, pr_id: str) -> List[Dict[str, Any]]:
|
282
305
|
"""
|
283
306
|
Get commits from a pull request
|
@@ -287,26 +310,28 @@ class BitbucketCloudApi(BitbucketApiAbstract):
|
|
287
310
|
List[Dict[str, Any]]: List of commits in the pull request
|
288
311
|
"""
|
289
312
|
return self.repository.pullrequests.get(pr_id).get('commits', {}).get('values', [])
|
290
|
-
|
291
|
-
def get_pull_request(self, pr_id: str) -> Any:
|
313
|
+
|
314
|
+
def get_pull_request(self, pr_id: str) -> Dict[str, Any]:
|
292
315
|
""" Get details of a pull request
|
293
316
|
Parameters:
|
294
317
|
pr_id(str): the pull request ID
|
295
318
|
Returns:
|
296
|
-
Any: Details of the pull request
|
319
|
+
Dict[str, Any]: Details of the pull request
|
297
320
|
"""
|
298
|
-
|
299
|
-
|
300
|
-
|
321
|
+
response = self.repository.pullrequests.get(pr_id)
|
322
|
+
return normalize_response(response)
|
323
|
+
|
324
|
+
def get_pull_requests_changes(self, pr_id: str) -> Dict[str, Any]:
|
301
325
|
"""
|
302
326
|
Get changes of a pull request
|
303
327
|
Parameters:
|
304
328
|
pr_id(str): the pull request ID
|
305
329
|
Returns:
|
306
|
-
Any: Changes of the pull request
|
330
|
+
Dict[str, Any]: Changes of the pull request
|
307
331
|
"""
|
308
|
-
|
309
|
-
|
332
|
+
response = self.repository.pullrequests.get(pr_id).get('diff', {})
|
333
|
+
return normalize_response(response)
|
334
|
+
|
310
335
|
def add_pull_request_comment(self, pr_id: str, content, inline=None) -> str:
|
311
336
|
"""
|
312
337
|
Add a comment to a pull request. Supports multiple content types and inline comments.
|
alita_sdk/tools/elitea_base.py
CHANGED
@@ -2,7 +2,7 @@ import ast
|
|
2
2
|
import fnmatch
|
3
3
|
import logging
|
4
4
|
import traceback
|
5
|
-
from typing import Any, Optional, List, Dict
|
5
|
+
from typing import Any, Optional, List, Dict, Generator
|
6
6
|
|
7
7
|
from langchain_core.documents import Document
|
8
8
|
from langchain_core.tools import ToolException
|
@@ -208,7 +208,7 @@ class BaseVectorStoreToolApiWrapper(BaseToolApiWrapper):
|
|
208
208
|
Document: The processed document with metadata."""
|
209
209
|
pass
|
210
210
|
|
211
|
-
def _process_documents(self, documents: List[Document]) ->
|
211
|
+
def _process_documents(self, documents: List[Document]) -> Generator[Document, None, None]:
|
212
212
|
"""
|
213
213
|
Process a list of base documents to extract relevant metadata for full document preparation.
|
214
214
|
Used for late processing of documents after we ensure that the documents have to be indexed to avoid
|
@@ -219,9 +219,14 @@ class BaseVectorStoreToolApiWrapper(BaseToolApiWrapper):
|
|
219
219
|
documents (List[Document]): The base documents to process.
|
220
220
|
|
221
221
|
Returns:
|
222
|
-
|
222
|
+
Generator[Document, None, None]: A generator yielding processed documents with metadata.
|
223
223
|
"""
|
224
|
-
|
224
|
+
for doc in documents:
|
225
|
+
processed_docs = self._process_document(doc)
|
226
|
+
if processed_docs: # Only proceed if the list is not empty
|
227
|
+
for processed_doc in processed_docs:
|
228
|
+
yield processed_doc
|
229
|
+
|
225
230
|
|
226
231
|
def _init_vector_store(self, collection_suffix: str = "", embeddings: Optional[Any] = None):
|
227
232
|
""" Initializes the vector store wrapper with the provided parameters."""
|