alita-sdk 0.3.182__py3-none-any.whl → 0.3.183__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/langchain/assistant.py +124 -2
- alita_sdk/runtime/langchain/langraph_agent.py +65 -8
- alita_sdk/runtime/llms/alita.py +1 -1
- alita_sdk/runtime/tools/llm.py +260 -41
- alita_sdk/runtime/tools/mcp_server_tool.py +15 -0
- alita_sdk/runtime/utils/streamlit.py +2 -1
- alita_sdk/tools/__init__.py +1 -0
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +55 -3
- alita_sdk/tools/ado/work_item/ado_wrapper.py +10 -6
- alita_sdk/tools/carrier/api_wrapper.py +3 -0
- alita_sdk/tools/carrier/backend_tests_tool.py +101 -7
- alita_sdk/tools/carrier/carrier_sdk.py +4 -0
- alita_sdk/tools/slack/__init__.py +57 -0
- alita_sdk/tools/slack/api_wrapper.py +190 -0
- {alita_sdk-0.3.182.dist-info → alita_sdk-0.3.183.dist-info}/METADATA +2 -1
- {alita_sdk-0.3.182.dist-info → alita_sdk-0.3.183.dist-info}/RECORD +19 -17
- {alita_sdk-0.3.182.dist-info → alita_sdk-0.3.183.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.182.dist-info → alita_sdk-0.3.183.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.182.dist-info → alita_sdk-0.3.183.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
|
|
14
14
|
|
15
15
|
ai_icon = b'<plain_txt_msg:img>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<!plain_txt_msg>'
|
16
16
|
user_icon = b'<plain_txt_msg:img>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<!plain_txt_msg>'
|
17
|
-
agent_types = ["pipeline", "react", "xml", "openai"]
|
17
|
+
agent_types = ["pipeline", "react", "xml", "openai", "predict"]
|
18
18
|
|
19
19
|
def img_to_txt(filename):
|
20
20
|
msg = b"<plain_txt_msg:img>"
|
@@ -1034,6 +1034,7 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
|
|
1034
1034
|
st.session_state.messages.append({"role": "user", "content": prompt})
|
1035
1035
|
with st.chat_message("assistant", avatar=ai_icon):
|
1036
1036
|
st_cb = AlitaStreamlitCallback(st)
|
1037
|
+
logger.info(st.session_state.messages)
|
1037
1038
|
response = st.session_state.agent_executor.invoke(
|
1038
1039
|
{"input": prompt, "chat_history": st.session_state.messages[:-1]},
|
1039
1040
|
{ 'callbacks': [st_cb], 'configurable': {"thread_id": st.session_state.thread_id}}
|
alita_sdk/tools/__init__.py
CHANGED
@@ -79,6 +79,7 @@ _safe_import_tool('pptx', 'pptx', 'get_tools', 'PPTXToolkit')
|
|
79
79
|
_safe_import_tool('postman', 'postman', 'get_tools', 'PostmanToolkit')
|
80
80
|
_safe_import_tool('memory', 'memory', 'get_tools', 'MemoryToolkit')
|
81
81
|
_safe_import_tool('zephyr_squad', 'zephyr_squad', 'get_tools', 'ZephyrSquadToolkit')
|
82
|
+
_safe_import_tool('slack', 'slack', 'get_tools', 'SlackToolkit')
|
82
83
|
|
83
84
|
# Log import summary
|
84
85
|
available_count = len(AVAILABLE_TOOLS)
|
@@ -10,7 +10,9 @@ from langchain_core.tools import ToolException
|
|
10
10
|
from msrest.authentication import BasicAuthentication
|
11
11
|
from pydantic import create_model, PrivateAttr, model_validator, SecretStr
|
12
12
|
from pydantic.fields import FieldInfo as Field
|
13
|
+
import xml.etree.ElementTree as ET
|
13
14
|
|
15
|
+
from ..work_item import AzureDevOpsApiWrapper
|
14
16
|
from ...elitea_base import BaseToolApiWrapper
|
15
17
|
|
16
18
|
logger = logging.getLogger(__name__)
|
@@ -101,6 +103,16 @@ TestCaseAddModel = create_model(
|
|
101
103
|
suite_id=(int, Field(description="ID of the test suite to which test cases are to be added"))
|
102
104
|
)
|
103
105
|
|
106
|
+
TestCaseCreateModel = create_model(
|
107
|
+
"TestCaseCreateModel",
|
108
|
+
project=(str, Field(description="Project ID or project name")),
|
109
|
+
plan_id=(int, Field(description="ID of the test plan to which test cases are to be added")),
|
110
|
+
suite_id=(int, Field(description="ID of the test suite to which test cases are to be added")),
|
111
|
+
title=(str, Field(description="Test case title")),
|
112
|
+
description=(str, Field(description="Test case description")),
|
113
|
+
test_steps=(str, Field(description="""Json array with test steps. Example: [{"action": "Some action", "expectedResult": "Some expectation"},...]""")),
|
114
|
+
)
|
115
|
+
|
104
116
|
TestCaseGetModel = create_model(
|
105
117
|
"TestCaseGetModel",
|
106
118
|
project=(str, Field(description="Project ID or project name")),
|
@@ -202,17 +214,51 @@ class TestPlanApiWrapper(BaseToolApiWrapper):
|
|
202
214
|
logger.error(f"Error getting test suite(s): {e}")
|
203
215
|
return ToolException(f"Error getting test suite(s): {e}")
|
204
216
|
|
205
|
-
def add_test_case(self, suite_test_case_create_update_parameters
|
217
|
+
def add_test_case(self, suite_test_case_create_update_parameters, project: str, plan_id: int, suite_id: int):
|
206
218
|
"""Add a test case to a suite in Azure DevOps."""
|
207
219
|
try:
|
208
|
-
|
209
|
-
|
220
|
+
if isinstance(suite_test_case_create_update_parameters, str):
|
221
|
+
suite_test_case_create_update_parameters = json.loads(suite_test_case_create_update_parameters)
|
222
|
+
suite_test_case_create_update_params_obj = [SuiteTestCaseCreateUpdateParameters(**param) for param in suite_test_case_create_update_parameters]
|
210
223
|
test_cases = self._client.add_test_cases_to_suite(suite_test_case_create_update_params_obj, project, plan_id, suite_id)
|
211
224
|
return [test_case.as_dict() for test_case in test_cases]
|
212
225
|
except Exception as e:
|
213
226
|
logger.error(f"Error adding test case: {e}")
|
214
227
|
return ToolException(f"Error adding test case: {e}")
|
215
228
|
|
229
|
+
def create_test_case(self, project: str, plan_id: int, suite_id: int, title: str, description: str, test_steps: str):
|
230
|
+
"""Creates a new test case in specified suite in Azure DevOps."""
|
231
|
+
work_item_wrapper = AzureDevOpsApiWrapper(organization_url=self.organization_url, token=self.token.get_secret_value(), project=project)
|
232
|
+
work_item_json = self.build_ado_test_case(title, description, json.loads(test_steps))
|
233
|
+
created_work_item_id = work_item_wrapper.create_work_item(work_item_json=json.dumps(work_item_json), wi_type="Test Case")['id']
|
234
|
+
return self.add_test_case([{"work_item":{"id":created_work_item_id}}], project, plan_id, suite_id)
|
235
|
+
|
236
|
+
def build_ado_test_case(self, title, description, steps):
|
237
|
+
"""
|
238
|
+
:param title: test title
|
239
|
+
:param description: test description
|
240
|
+
:param steps: steps [(action, expected result), ...]
|
241
|
+
:return: JSON with ADO fields
|
242
|
+
"""
|
243
|
+
steps_elem = ET.Element("steps")
|
244
|
+
|
245
|
+
for idx, step in enumerate(steps, start=1):
|
246
|
+
step_elem = ET.SubElement(steps_elem, "step", id=str(idx), type="Action")
|
247
|
+
action_elem = ET.SubElement(step_elem, "parameterizedString", isformatted="true")
|
248
|
+
action_elem.text = step["action"]
|
249
|
+
expected_elem = ET.SubElement(step_elem, "parameterizedString", isformatted="true")
|
250
|
+
expected_elem.text = step["expectedResult"]
|
251
|
+
|
252
|
+
steps_xml = ET.tostring(steps_elem, encoding="unicode")
|
253
|
+
|
254
|
+
return {
|
255
|
+
"fields": {
|
256
|
+
"System.Title": title,
|
257
|
+
"System.Description": description,
|
258
|
+
"Microsoft.VSTS.TCM.Steps": steps_xml
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
216
262
|
def get_test_case(self, project: str, plan_id: int, suite_id: int, test_case_id: str):
|
217
263
|
"""Get a test case from a suite in Azure DevOps."""
|
218
264
|
try:
|
@@ -280,6 +326,12 @@ class TestPlanApiWrapper(BaseToolApiWrapper):
|
|
280
326
|
"args_schema": TestCaseAddModel,
|
281
327
|
"ref": self.add_test_case,
|
282
328
|
},
|
329
|
+
{
|
330
|
+
"name": "create_test_case",
|
331
|
+
"description": self.create_test_case.__doc__,
|
332
|
+
"args_schema": TestCaseCreateModel,
|
333
|
+
"ref": self.create_test_case,
|
334
|
+
},
|
283
335
|
{
|
284
336
|
"name": "get_test_case",
|
285
337
|
"description": self.get_test_case.__doc__,
|
@@ -150,14 +150,15 @@ class AzureDevOpsApiWrapper(BaseToolApiWrapper):
|
|
150
150
|
|
151
151
|
return parsed_items
|
152
152
|
|
153
|
-
def _transform_work_item(self, work_item_json
|
153
|
+
def _transform_work_item(self, work_item_json):
|
154
154
|
try:
|
155
155
|
# Convert the input JSON to a Python dictionary
|
156
|
-
|
156
|
+
if isinstance(work_item_json, str):
|
157
|
+
work_item_json = json.loads(work_item_json)
|
157
158
|
except (json.JSONDecodeError, ValueError) as e:
|
158
159
|
raise ToolException(f"Issues during attempt to parse work_item_json: {e}")
|
159
160
|
|
160
|
-
if 'fields' not in
|
161
|
+
if 'fields' not in work_item_json:
|
161
162
|
raise ToolException("The 'fields' property is missing from the work_item_json.")
|
162
163
|
|
163
164
|
# Transform the dictionary into a list of JsonPatchOperation objects
|
@@ -167,11 +168,11 @@ class AzureDevOpsApiWrapper(BaseToolApiWrapper):
|
|
167
168
|
"path": f"/fields/{field}",
|
168
169
|
"value": value
|
169
170
|
}
|
170
|
-
for field, value in
|
171
|
+
for field, value in work_item_json["fields"].items()
|
171
172
|
]
|
172
173
|
return patch_document
|
173
174
|
|
174
|
-
def create_work_item(self, work_item_json
|
175
|
+
def create_work_item(self, work_item_json, wi_type="Task"):
|
175
176
|
"""Create a work item in Azure DevOps."""
|
176
177
|
try:
|
177
178
|
patch_document = self._transform_work_item(work_item_json)
|
@@ -185,7 +186,10 @@ class AzureDevOpsApiWrapper(BaseToolApiWrapper):
|
|
185
186
|
project=self.project,
|
186
187
|
type=wi_type
|
187
188
|
)
|
188
|
-
return
|
189
|
+
return {
|
190
|
+
"id": work_item.id,
|
191
|
+
"message": f"Work item {work_item.id} created successfully. View it at {work_item.url}."
|
192
|
+
}
|
189
193
|
except Exception as e:
|
190
194
|
if "unknown value" in str(e):
|
191
195
|
logger.error(f"Unable to create work item due to incorrect assignee: {e}")
|
@@ -70,6 +70,9 @@ class CarrierAPIWrapper(BaseModel):
|
|
70
70
|
def get_integrations(self, name: str):
|
71
71
|
return self._client.get_integrations(name)
|
72
72
|
|
73
|
+
def get_available_locations(self):
|
74
|
+
return self._client.get_available_locations()
|
75
|
+
|
73
76
|
def run_test(self, test_id: str, json_body):
|
74
77
|
return self._client.run_test(test_id, json_body)
|
75
78
|
|
@@ -79,8 +79,8 @@ class RunTestByIDTool(BaseTool):
|
|
79
79
|
description: str = "Execute test plan from the Carrier platform."
|
80
80
|
args_schema: Type[BaseModel] = create_model(
|
81
81
|
"RunTestByIdInput",
|
82
|
-
test_id=(
|
83
|
-
name=(str, Field(default=None, description="Test name to execute")),
|
82
|
+
test_id=(int, Field(default=None, description="Test id to execute. Use test_id if user provide id in int format")),
|
83
|
+
name=(str, Field(default=None, description="Test name to execute. Use name if user provide name in str format")),
|
84
84
|
test_parameters=(list, Field(
|
85
85
|
default=None,
|
86
86
|
description=(
|
@@ -89,9 +89,26 @@ class RunTestByIDTool(BaseTool):
|
|
89
89
|
"contain parameter names and their values."
|
90
90
|
)
|
91
91
|
)),
|
92
|
+
location=(str, Field(
|
93
|
+
default=None,
|
94
|
+
description=(
|
95
|
+
"Location to execute the test. Choose from public_regions, project_regions, "
|
96
|
+
"or cloud_regions. For cloud_regions, additional parameters may be required."
|
97
|
+
)
|
98
|
+
)),
|
99
|
+
cloud_settings=(dict, Field(
|
100
|
+
default={},
|
101
|
+
description=(
|
102
|
+
"Additional parameters for cloud_regions. Provide as a dictionary, "
|
103
|
+
"e.g., {'region_name': 'us-west-1', 'instance_type': 't2.large'}. "
|
104
|
+
"Don't provide this parameter as string! It should be a dictionary!"
|
105
|
+
"If no changes are needed, respond with 'use default'."
|
106
|
+
"Ensure these settings are passed as a valid dictionary not string"
|
107
|
+
)
|
108
|
+
)),
|
92
109
|
)
|
93
110
|
|
94
|
-
def _run(self, test_id=None, name=None, test_parameters=None):
|
111
|
+
def _run(self, test_id=None, name=None, test_parameters=None, location=None, cloud_settings=None):
|
95
112
|
try:
|
96
113
|
if not test_id and not name:
|
97
114
|
return {"message": "Please provide test id or test name to start"}
|
@@ -115,9 +132,18 @@ class RunTestByIDTool(BaseTool):
|
|
115
132
|
# If no test_parameters are provided, return the default ones for confirmation
|
116
133
|
if test_parameters is None:
|
117
134
|
return {
|
118
|
-
"message": "
|
135
|
+
"message": "The test requires confirmation or customization of the following parameters before execution.",
|
119
136
|
"default_test_parameters": default_test_parameters,
|
120
|
-
"instruction":
|
137
|
+
"instruction": (
|
138
|
+
"If the user has already indicated that default parameters should be used, "
|
139
|
+
"pass 'default_test_parameters' as 'test_parameters' and invoke the '_run' method again without prompting the user.\n"
|
140
|
+
"If the user wants to proceed with default parameters, respond with 'use default'.\n"
|
141
|
+
"In this case, the agent should pass 'default_test_parameters' as 'test_parameters' to the tool.\n"
|
142
|
+
"If the user provides specific overrides, parse them into a list of dictionaries in the following format:\n"
|
143
|
+
"[{'vUsers': '5', 'duration': '120'}].\n"
|
144
|
+
"Each dictionary should contain the parameter name and its desired value.\n"
|
145
|
+
"Ensure that you correctly parse and validate the user's input before invoking the '_run' method."
|
146
|
+
),
|
121
147
|
}
|
122
148
|
|
123
149
|
# Normalize test_parameters if provided in an incorrect format
|
@@ -126,6 +152,55 @@ class RunTestByIDTool(BaseTool):
|
|
126
152
|
# Apply user-provided test parameters
|
127
153
|
updated_test_parameters = self._apply_test_parameters(default_test_parameters, test_parameters)
|
128
154
|
|
155
|
+
# Fetch available locations
|
156
|
+
available_locations = self.api_wrapper.get_available_locations()
|
157
|
+
# If location is not provided, prompt the user with available options
|
158
|
+
if not location:
|
159
|
+
return {
|
160
|
+
"message": "Please select a location to execute the test.",
|
161
|
+
"available_locations": {
|
162
|
+
"public_regions": available_locations["public_regions"],
|
163
|
+
"project_regions": available_locations["project_regions"],
|
164
|
+
"cloud_regions": [region["name"] for region in available_locations["cloud_regions"]],
|
165
|
+
},
|
166
|
+
"instruction": (
|
167
|
+
"For public_regions and project_regions, provide the region name. "
|
168
|
+
"For cloud_regions, provide the region name and optionally override cloud_settings."
|
169
|
+
)
|
170
|
+
}
|
171
|
+
|
172
|
+
# Handle cloud_regions with additional parameters
|
173
|
+
selected_cloud_region = next(
|
174
|
+
(region for region in available_locations["cloud_regions"] if region["name"] == location),
|
175
|
+
None
|
176
|
+
)
|
177
|
+
|
178
|
+
if selected_cloud_region:
|
179
|
+
# Extract available cloud_settings from the selected cloud region
|
180
|
+
available_cloud_settings = selected_cloud_region["cloud_settings"]
|
181
|
+
|
182
|
+
# Add default values for instance_type and ec2_instance_type
|
183
|
+
available_cloud_settings["instance_type"] = "spot"
|
184
|
+
available_cloud_settings["ec2_instance_type"] = "t2.medium"
|
185
|
+
|
186
|
+
# If cloud_settings are not provided, prompt the user with available parameters
|
187
|
+
if not cloud_settings:
|
188
|
+
return {
|
189
|
+
"message": f"Please confirm or override the following cloud settings for the selected location: {location}",
|
190
|
+
"available_cloud_settings": available_cloud_settings,
|
191
|
+
"instruction": (
|
192
|
+
"Provide a dictionary to override cloud settings, e.g., "
|
193
|
+
"{'region_name': 'us-west-1', 'instance_type': 't2.large'}. "
|
194
|
+
"Don't provide this parameter as string! It should be a dictionary! "
|
195
|
+
"Ensure these settings are passed to the 'cloud_settings' argument, not 'test_parameters'."
|
196
|
+
"Ensure these settings are passed as a valid dictionary not string"
|
197
|
+
)
|
198
|
+
}
|
199
|
+
|
200
|
+
# Validate and merge user-provided cloud_settings with available parameters
|
201
|
+
cloud_settings = self._merge_cloud_settings(available_cloud_settings, cloud_settings)
|
202
|
+
|
203
|
+
|
129
204
|
# Build common_params dictionary
|
130
205
|
common_params = {
|
131
206
|
param["name"]: param
|
@@ -136,7 +211,8 @@ class RunTestByIDTool(BaseTool):
|
|
136
211
|
# Add env_vars, parallel_runners, and location to common_params
|
137
212
|
common_params["env_vars"] = test_data.get("env_vars", {})
|
138
213
|
common_params["parallel_runners"] = test_data.get("parallel_runners")
|
139
|
-
common_params["location"] =
|
214
|
+
common_params["location"] = location
|
215
|
+
common_params["env_vars"]["cloud_settings"] = cloud_settings or {}
|
140
216
|
|
141
217
|
# Build the JSON body
|
142
218
|
json_body = {
|
@@ -146,7 +222,7 @@ class RunTestByIDTool(BaseTool):
|
|
146
222
|
}
|
147
223
|
|
148
224
|
# Execute the test
|
149
|
-
report_id = self.api_wrapper.run_test(
|
225
|
+
report_id = self.api_wrapper.run_test(test_data.get("id"), json_body)
|
150
226
|
return f"Test started. Report id: {report_id}. Link to report:" \
|
151
227
|
f"{self.api_wrapper.url.rstrip('/')}/-/performance/backend/results?result_id={report_id}"
|
152
228
|
|
@@ -207,6 +283,24 @@ class RunTestByIDTool(BaseTool):
|
|
207
283
|
})
|
208
284
|
return updated_parameters
|
209
285
|
|
286
|
+
def _merge_cloud_settings(self, available_cloud_settings, user_cloud_settings):
|
287
|
+
"""
|
288
|
+
Merge user-provided cloud settings with available cloud settings.
|
289
|
+
Ensure that user-provided values override the defaults.
|
290
|
+
"""
|
291
|
+
if not user_cloud_settings:
|
292
|
+
return available_cloud_settings
|
293
|
+
|
294
|
+
# Validate user-provided keys against available keys
|
295
|
+
invalid_keys = [key for key in user_cloud_settings if key not in available_cloud_settings]
|
296
|
+
if invalid_keys:
|
297
|
+
raise ValueError(
|
298
|
+
f"Invalid keys in cloud settings: {invalid_keys}. Allowed keys: {list(available_cloud_settings.keys())}")
|
299
|
+
|
300
|
+
# Merge the settings
|
301
|
+
merged_settings = {**available_cloud_settings, **user_cloud_settings}
|
302
|
+
return merged_settings
|
303
|
+
|
210
304
|
|
211
305
|
class CreateBackendTestInput(BaseModel):
|
212
306
|
test_name: str = Field(..., description="Test name")
|
@@ -103,6 +103,10 @@ class CarrierClient(BaseModel):
|
|
103
103
|
endpoint = f"api/v1/integrations/integrations/{self.credentials.project_id}?name={name}"
|
104
104
|
return self.request('get', endpoint)
|
105
105
|
|
106
|
+
def get_available_locations(self):
|
107
|
+
endpoint = f"api/v1/shared/locations/default/{self.credentials.project_id}"
|
108
|
+
return self.request('get', endpoint)
|
109
|
+
|
106
110
|
def run_ui_test(self, test_id: str, json_body):
|
107
111
|
"""Run a UI test with the given test ID and JSON body."""
|
108
112
|
endpoint = f"api/v1/ui_performance/test/{self.credentials.project_id}/{test_id}"
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
from langchain_core.tools import BaseToolkit, BaseTool
|
4
|
+
from pydantic import create_model, BaseModel, Field, SecretStr
|
5
|
+
from ..base.tool import BaseAction
|
6
|
+
|
7
|
+
from .api_wrapper import SlackApiWrapper
|
8
|
+
from ..utils import TOOLKIT_SPLITTER, clean_string, get_max_toolkit_length
|
9
|
+
|
10
|
+
name = "slack"
|
11
|
+
|
12
|
+
def get_tools(tool):
|
13
|
+
return SlackToolkit().get_toolkit(
|
14
|
+
selected_tools=tool['settings'].get('selected_tools', []),
|
15
|
+
slack_token=tool['settings'].get('slack_token'),
|
16
|
+
channel_id=tool['settings'].get('channel_id'),
|
17
|
+
toolkit_name=tool.get('toolkit_name')
|
18
|
+
).get_tools()
|
19
|
+
|
20
|
+
class SlackToolkit(BaseToolkit):
|
21
|
+
tools: List[BaseTool] = []
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def toolkit_config_schema() -> BaseModel:
|
25
|
+
selected_tools = {x['name']: x['args_schema'].schema() for x in SlackApiWrapper.model_construct().get_available_tools()}
|
26
|
+
SlackToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
|
27
|
+
return create_model(
|
28
|
+
name,
|
29
|
+
slack_token=(SecretStr, Field( description="Slack Bot/User OAuth Token like XOXB-*****-*****-*****-*****")),
|
30
|
+
channel_id=(str, Field(title="Channel ID", description="Channel ID, user ID, or conversation ID to send the message to. (like C12345678 for public channels, D12345678 for DMs)")),
|
31
|
+
selected_tools=(list[str], Field(title="Selected Tools", description="List of tools to enable", default=[])),
|
32
|
+
__config__={'json_schema_extra': {'metadata': {"label": "slack", "icon_url": None, "hidden": True}}}
|
33
|
+
)
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def get_toolkit(cls, selected_tools: Optional[List[str]] = None, toolkit_name: Optional[str] = None, **kwargs):
|
37
|
+
if selected_tools is None:
|
38
|
+
selected_tools = []
|
39
|
+
slack_api_wrapper = SlackApiWrapper(**kwargs)
|
40
|
+
prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
|
41
|
+
available_tools = slack_api_wrapper.get_available_tools()
|
42
|
+
tools = []
|
43
|
+
for tool in available_tools:
|
44
|
+
if selected_tools and tool["name"] not in selected_tools:
|
45
|
+
continue
|
46
|
+
tools.append(BaseAction(
|
47
|
+
api_wrapper=slack_api_wrapper,
|
48
|
+
name=prefix + tool["name"],
|
49
|
+
description=tool["description"],
|
50
|
+
args_schema=tool["args_schema"],
|
51
|
+
func=tool["ref"]
|
52
|
+
))
|
53
|
+
return cls(tools=tools)
|
54
|
+
|
55
|
+
def get_tools(self) -> List[BaseTool]:
|
56
|
+
return self.tools
|
57
|
+
|
@@ -0,0 +1,190 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional
|
3
|
+
from pydantic import BaseModel, Field, SecretStr, create_model, model_validator
|
4
|
+
from slack_sdk import WebClient
|
5
|
+
from slack_sdk.errors import SlackApiError
|
6
|
+
|
7
|
+
from alita_sdk.tools.elitea_base import BaseToolApiWrapper
|
8
|
+
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
SendMessageModel = create_model(
|
13
|
+
"SendMessageModel",
|
14
|
+
message=(str, Field(description="The message text to send."))
|
15
|
+
)
|
16
|
+
|
17
|
+
ReadMessagesModel = create_model(
|
18
|
+
"ReadMessagesModel",
|
19
|
+
limit=(int, Field(default=10, description="The number of messages to fetch (default is 10)."))
|
20
|
+
)
|
21
|
+
|
22
|
+
CreateChannelModel = create_model(
|
23
|
+
"CreateChannelModel",
|
24
|
+
channel_name=(str, Field(description="Channel ID, user ID, or conversation ID to send the message to. (like C12345678 for public channels, D12345678 for DMs)")),
|
25
|
+
is_private=(bool, Field(default=False, description="Whether to make the channel private (default: False)."))
|
26
|
+
)
|
27
|
+
|
28
|
+
ListUsersModel = create_model(
|
29
|
+
"ListUsersModel"
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
class SlackApiWrapper(BaseToolApiWrapper):
|
34
|
+
|
35
|
+
"""
|
36
|
+
Slack API wrapper for interacting with Slack channels and messages.
|
37
|
+
"""
|
38
|
+
slack_token: Optional[SecretStr] = Field(default=None,description="Slack Bot/User OAuth Token like XOXB-*****-*****-*****-*****")
|
39
|
+
channel_id: Optional[str] = Field(default=None, description="Channel ID, user ID, or conversation ID to send the message to. (like C12345678 for public channels, D12345678 for DMs)")
|
40
|
+
|
41
|
+
@model_validator(mode="after")
|
42
|
+
@classmethod
|
43
|
+
def validate_toolkit(cls, values):
|
44
|
+
token = values.slack_token.get_secret_value() if values.slack_token else None
|
45
|
+
if not token:
|
46
|
+
logging.error("Slack token is required.")
|
47
|
+
raise ValueError("Slack token is required.")
|
48
|
+
try:
|
49
|
+
cls._client = WebClient(token=token)
|
50
|
+
logging.info("Authenticated with Slack token.")
|
51
|
+
except Exception as e:
|
52
|
+
logging.error(f"Failed to authenticate with Slack: {str(e)}")
|
53
|
+
raise ValueError(f"Failed to authenticate with Slack: {str(e)}")
|
54
|
+
return values
|
55
|
+
|
56
|
+
def _get_client(self):
|
57
|
+
if not self._client:
|
58
|
+
self._client = WebClient(token=self.slack_token.get_secret_value())
|
59
|
+
return self._client
|
60
|
+
|
61
|
+
|
62
|
+
def send_message(self, message: str):
|
63
|
+
"""
|
64
|
+
Sends a message to a specified Slack channel, user, or conversation.
|
65
|
+
"""
|
66
|
+
|
67
|
+
try:
|
68
|
+
|
69
|
+
client = self._get_client()
|
70
|
+
response = client.chat_postMessage(channel=self.channel_id, text=message)
|
71
|
+
logger.info(f"Message sent to {self.channel_id}: {message}")
|
72
|
+
return f"Message sent successfully to {self.channel_id}."
|
73
|
+
|
74
|
+
except SlackApiError as e:
|
75
|
+
logger.error(f"Failed to send message to {self.channel_id}: {e.response['error']}")
|
76
|
+
return f"Received the error : {e.response['error']}"
|
77
|
+
|
78
|
+
def read_messages(self, limit=10):
|
79
|
+
"""
|
80
|
+
Reads the latest messages from a Slack channel or conversation.
|
81
|
+
|
82
|
+
:param limit: int: The number of messages to fetch (default is 10)
|
83
|
+
:return: list: Returns a list of messages with metadata.
|
84
|
+
"""
|
85
|
+
try:
|
86
|
+
|
87
|
+
client = self._get_client()
|
88
|
+
# Fetch conversation history
|
89
|
+
response = client.conversations_history(
|
90
|
+
channel=self.channel_id,
|
91
|
+
limit=limit )
|
92
|
+
|
93
|
+
# Extract messages from the response
|
94
|
+
messages = self.extract_slack_messages(response.get('messages', []))
|
95
|
+
|
96
|
+
return messages
|
97
|
+
|
98
|
+
except SlackApiError as e:
|
99
|
+
# Handle errors from the Slack API
|
100
|
+
logger.error(f"Failed to read message from {self.channel_id}: {e.response['error']}")
|
101
|
+
return f"Received the error : {e.response['error']}"
|
102
|
+
|
103
|
+
def create_slack_channel(self, channel_name: str, is_private=False):
|
104
|
+
"""
|
105
|
+
Creates a new Slack channel.
|
106
|
+
|
107
|
+
:param channel_name: str: Desired name for the channel (e.g., "my-new-channel").
|
108
|
+
:param is_private: bool: Whether to make the channel private (default: False).
|
109
|
+
:return: dict: Slack API response or error message.
|
110
|
+
"""
|
111
|
+
|
112
|
+
try:
|
113
|
+
client = self._get_client()
|
114
|
+
response = client.conversations_create(
|
115
|
+
name=channel_name,
|
116
|
+
is_private=is_private
|
117
|
+
)
|
118
|
+
channel_id = response["channel"]["id"]
|
119
|
+
print(f"Channel '{channel_name}' created successfully! Channel ID: {channel_id}")
|
120
|
+
return {"success": True, "channel_id": channel_id}
|
121
|
+
except SlackApiError as e:
|
122
|
+
error_message = e.response.get("error", "unknown_error")
|
123
|
+
print(f"Failed to create channel '{channel_name}': {error_message}")
|
124
|
+
return {"success": False, "error": error_message}
|
125
|
+
|
126
|
+
def list_users(self):
|
127
|
+
"""
|
128
|
+
Lists all users in the Slack workspace.
|
129
|
+
|
130
|
+
:return: list: List of users with their IDs and names.
|
131
|
+
"""
|
132
|
+
|
133
|
+
|
134
|
+
try:
|
135
|
+
client = self._get_client()
|
136
|
+
print(client.auth_test())
|
137
|
+
response = client.users_list()
|
138
|
+
users = response["members"]
|
139
|
+
return [{"id": user["id"], "name": user["name"]} for user in users if not user["is_bot"]]
|
140
|
+
|
141
|
+
except SlackApiError as e:
|
142
|
+
logger.error(f"Failed to list users: {e.response['error']}")
|
143
|
+
return f"Received the error : {e.response['error']}"
|
144
|
+
|
145
|
+
def extract_slack_messages(self, data):
|
146
|
+
extracted_info = []
|
147
|
+
|
148
|
+
for item in data:
|
149
|
+
# Extract 'user' and 'text'
|
150
|
+
user = item.get("user", "Undefined User")
|
151
|
+
message = item.get("text", "No message")
|
152
|
+
|
153
|
+
# Extract 'app name'
|
154
|
+
app_name = item.get("bot_profile", {}).get("name", "No App Name")
|
155
|
+
|
156
|
+
# Append to result
|
157
|
+
extracted_info.append({"user": user, "message": message, "app_name": app_name})
|
158
|
+
|
159
|
+
return extracted_info
|
160
|
+
|
161
|
+
|
162
|
+
def get_available_tools(self):
|
163
|
+
return [
|
164
|
+
{
|
165
|
+
"name": "send_message",
|
166
|
+
"description": self.send_message.__doc__ or "Send a message to a Slack channel, user, or conversation.",
|
167
|
+
"args_schema": SendMessageModel,
|
168
|
+
"ref": self.send_message
|
169
|
+
|
170
|
+
},
|
171
|
+
{
|
172
|
+
"name": "read_messages",
|
173
|
+
"description": self.read_messages.__doc__ or "Send a message to a Slack channel, user, or conversation.",
|
174
|
+
"args_schema": ReadMessagesModel,
|
175
|
+
"ref": self.read_messages
|
176
|
+
},
|
177
|
+
{
|
178
|
+
"name": "create_channel",
|
179
|
+
"description": self.create_slack_channel.__doc__ or "Send a message to a Slack channel, user, or conversation.",
|
180
|
+
"args_schema": CreateChannelModel,
|
181
|
+
"ref": self.create_slack_channel
|
182
|
+
},
|
183
|
+
{
|
184
|
+
"name": "list_users",
|
185
|
+
"description": self.list_users.__doc__ or "List all users in the Slack workspace.",
|
186
|
+
"args_schema": ListUsersModel,
|
187
|
+
"ref": self.list_users
|
188
|
+
}
|
189
|
+
|
190
|
+
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: alita_sdk
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.183
|
4
4
|
Summary: SDK for building langchain agents using resources from Alita
|
5
5
|
Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedjik@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
|
6
6
|
License-Expression: Apache-2.0
|
@@ -124,6 +124,7 @@ Requires-Dist: shortuuid==1.0.13; extra == "tools"
|
|
124
124
|
Requires-Dist: yarl==1.17.1; extra == "tools"
|
125
125
|
Requires-Dist: langmem==0.0.27; extra == "tools"
|
126
126
|
Requires-Dist: textract-py3==2.1.1; extra == "tools"
|
127
|
+
Requires-Dist: slack_sdk==3.35.0; extra == "tools"
|
127
128
|
Provides-Extra: community
|
128
129
|
Requires-Dist: retry-extended==0.2.3; extra == "community"
|
129
130
|
Requires-Dist: pyobjtojson==0.3; extra == "community"
|