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.
Files changed (42) hide show
  1. alita_sdk/runtime/clients/client.py +314 -11
  2. alita_sdk/runtime/langchain/assistant.py +22 -21
  3. alita_sdk/runtime/langchain/interfaces/llm_processor.py +1 -4
  4. alita_sdk/runtime/langchain/langraph_agent.py +6 -1
  5. alita_sdk/runtime/langchain/store_manager.py +4 -4
  6. alita_sdk/runtime/toolkits/application.py +5 -10
  7. alita_sdk/runtime/toolkits/tools.py +11 -21
  8. alita_sdk/runtime/tools/vectorstore.py +25 -11
  9. alita_sdk/runtime/utils/streamlit.py +505 -222
  10. alita_sdk/runtime/utils/toolkit_runtime.py +147 -0
  11. alita_sdk/runtime/utils/toolkit_utils.py +157 -0
  12. alita_sdk/runtime/utils/utils.py +5 -0
  13. alita_sdk/tools/__init__.py +2 -0
  14. alita_sdk/tools/ado/repos/repos_wrapper.py +20 -13
  15. alita_sdk/tools/bitbucket/api_wrapper.py +5 -5
  16. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +54 -29
  17. alita_sdk/tools/elitea_base.py +9 -4
  18. alita_sdk/tools/gitlab/__init__.py +22 -10
  19. alita_sdk/tools/gitlab/api_wrapper.py +278 -253
  20. alita_sdk/tools/gitlab/tools.py +354 -376
  21. alita_sdk/tools/llm/llm_utils.py +0 -6
  22. alita_sdk/tools/memory/__init__.py +54 -10
  23. alita_sdk/tools/openapi/__init__.py +14 -3
  24. alita_sdk/tools/sharepoint/__init__.py +2 -1
  25. alita_sdk/tools/sharepoint/api_wrapper.py +11 -3
  26. alita_sdk/tools/testrail/api_wrapper.py +39 -16
  27. alita_sdk/tools/utils/content_parser.py +77 -13
  28. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/METADATA +1 -1
  29. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/RECORD +32 -40
  30. alita_sdk/community/analysis/__init__.py +0 -0
  31. alita_sdk/community/analysis/ado_analyse/__init__.py +0 -103
  32. alita_sdk/community/analysis/ado_analyse/api_wrapper.py +0 -261
  33. alita_sdk/community/analysis/github_analyse/__init__.py +0 -98
  34. alita_sdk/community/analysis/github_analyse/api_wrapper.py +0 -166
  35. alita_sdk/community/analysis/gitlab_analyse/__init__.py +0 -110
  36. alita_sdk/community/analysis/gitlab_analyse/api_wrapper.py +0 -172
  37. alita_sdk/community/analysis/jira_analyse/__init__.py +0 -141
  38. alita_sdk/community/analysis/jira_analyse/api_wrapper.py +0 -252
  39. alita_sdk/runtime/llms/alita.py +0 -259
  40. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/WHEEL +0 -0
  41. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/licenses/LICENSE +0 -0
  42. {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
@@ -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
 
@@ -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
- directory_path: str = "",
307
- branch_name: str = None,
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
- Params:
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=branch_name, version_type="branch"
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=directory_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(files)
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
- directory_path=directory_path,
406
- branch_name=self.active_branch if self.active_branch else self.base_branch,
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 BaseModel, model_validator, SecretStr
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
- Any: Details of the pull request
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
- Any: Changes of the pull request
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
- sub_folder=file_path)
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 = file_path, branch=branch)
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
- source_commit_generator = self.api_client.get_commits(project_key=self.project, repository_slug=self.repository, hash_newest=branch, limit=1)
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, pull_request_id=pr_id)
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
- return self.api_client.get_pull_request(project_key=self.project, repository_slug=self.repository, pull_request_id=pr_id)
160
-
161
- def get_pull_requests_changes(self, pr_id: str) -> Any:
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
- return self.api_client.get_pull_requests_changes(project_key=self.project, repository_slug=self.repository, pull_request_id=pr_id)
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
- self.repository.get(path=f'src/{branch}/{file_path}?max_depth=100&pagelen=100&fields=values.path&q=type="commit_file"')[
253
- 'values']:
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={}, headers={'Content-Type': 'application/x-www-form-urlencoded'})
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
- return self.repository.pullrequests.get(pr_id)
299
-
300
- def get_pull_requests_changes(self, pr_id: str) -> Any:
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
- return self.repository.pullrequests.get(pr_id).get('diff', {})
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.
@@ -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]) -> 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
- List[Document]: The processed documents with metadata.
222
+ Generator[Document, None, None]: A generator yielding processed documents with metadata.
223
223
  """
224
- return [self._process_document(doc) for doc in documents]
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."""