alita-sdk 0.3.532__py3-none-any.whl → 0.3.602__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (137) hide show
  1. alita_sdk/cli/agent_executor.py +2 -1
  2. alita_sdk/cli/agent_loader.py +34 -4
  3. alita_sdk/cli/agents.py +433 -203
  4. alita_sdk/community/__init__.py +8 -4
  5. alita_sdk/configurations/__init__.py +1 -0
  6. alita_sdk/configurations/openapi.py +323 -0
  7. alita_sdk/runtime/clients/client.py +165 -7
  8. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  9. alita_sdk/runtime/langchain/assistant.py +61 -11
  10. alita_sdk/runtime/langchain/constants.py +419 -171
  11. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -2
  12. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  13. alita_sdk/runtime/langchain/langraph_agent.py +108 -23
  14. alita_sdk/runtime/langchain/utils.py +76 -14
  15. alita_sdk/runtime/skills/__init__.py +91 -0
  16. alita_sdk/runtime/skills/callbacks.py +498 -0
  17. alita_sdk/runtime/skills/discovery.py +540 -0
  18. alita_sdk/runtime/skills/executor.py +610 -0
  19. alita_sdk/runtime/skills/input_builder.py +371 -0
  20. alita_sdk/runtime/skills/models.py +330 -0
  21. alita_sdk/runtime/skills/registry.py +355 -0
  22. alita_sdk/runtime/skills/skill_runner.py +330 -0
  23. alita_sdk/runtime/toolkits/__init__.py +5 -0
  24. alita_sdk/runtime/toolkits/artifact.py +2 -1
  25. alita_sdk/runtime/toolkits/mcp.py +6 -3
  26. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  27. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  28. alita_sdk/runtime/toolkits/tools.py +139 -10
  29. alita_sdk/runtime/toolkits/vectorstore.py +1 -1
  30. alita_sdk/runtime/tools/__init__.py +3 -1
  31. alita_sdk/runtime/tools/artifact.py +15 -0
  32. alita_sdk/runtime/tools/data_analysis.py +183 -0
  33. alita_sdk/runtime/tools/llm.py +260 -73
  34. alita_sdk/runtime/tools/loop.py +3 -1
  35. alita_sdk/runtime/tools/loop_output.py +3 -1
  36. alita_sdk/runtime/tools/mcp_server_tool.py +6 -3
  37. alita_sdk/runtime/tools/router.py +2 -4
  38. alita_sdk/runtime/tools/sandbox.py +9 -6
  39. alita_sdk/runtime/tools/skill_router.py +776 -0
  40. alita_sdk/runtime/tools/tool.py +3 -1
  41. alita_sdk/runtime/tools/vectorstore.py +7 -2
  42. alita_sdk/runtime/tools/vectorstore_base.py +7 -2
  43. alita_sdk/runtime/utils/constants.py +5 -1
  44. alita_sdk/runtime/utils/mcp_client.py +1 -1
  45. alita_sdk/runtime/utils/mcp_sse_client.py +1 -1
  46. alita_sdk/runtime/utils/toolkit_utils.py +2 -0
  47. alita_sdk/tools/__init__.py +44 -2
  48. alita_sdk/tools/ado/repos/__init__.py +26 -8
  49. alita_sdk/tools/ado/repos/repos_wrapper.py +78 -52
  50. alita_sdk/tools/ado/test_plan/__init__.py +3 -2
  51. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  52. alita_sdk/tools/ado/utils.py +1 -18
  53. alita_sdk/tools/ado/wiki/__init__.py +2 -1
  54. alita_sdk/tools/ado/wiki/ado_wrapper.py +23 -1
  55. alita_sdk/tools/ado/work_item/__init__.py +3 -2
  56. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  57. alita_sdk/tools/advanced_jira_mining/__init__.py +2 -1
  58. alita_sdk/tools/aws/delta_lake/__init__.py +2 -1
  59. alita_sdk/tools/azure_ai/search/__init__.py +2 -1
  60. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  61. alita_sdk/tools/base_indexer_toolkit.py +51 -30
  62. alita_sdk/tools/bitbucket/__init__.py +2 -1
  63. alita_sdk/tools/bitbucket/api_wrapper.py +1 -1
  64. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +3 -3
  65. alita_sdk/tools/browser/__init__.py +1 -1
  66. alita_sdk/tools/carrier/__init__.py +1 -1
  67. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  68. alita_sdk/tools/cloud/aws/__init__.py +2 -1
  69. alita_sdk/tools/cloud/azure/__init__.py +2 -1
  70. alita_sdk/tools/cloud/gcp/__init__.py +2 -1
  71. alita_sdk/tools/cloud/k8s/__init__.py +2 -1
  72. alita_sdk/tools/code/linter/__init__.py +2 -1
  73. alita_sdk/tools/code/sonar/__init__.py +2 -1
  74. alita_sdk/tools/code_indexer_toolkit.py +19 -2
  75. alita_sdk/tools/confluence/__init__.py +7 -6
  76. alita_sdk/tools/confluence/api_wrapper.py +7 -8
  77. alita_sdk/tools/confluence/loader.py +4 -2
  78. alita_sdk/tools/custom_open_api/__init__.py +2 -1
  79. alita_sdk/tools/elastic/__init__.py +2 -1
  80. alita_sdk/tools/elitea_base.py +28 -9
  81. alita_sdk/tools/figma/__init__.py +52 -6
  82. alita_sdk/tools/figma/api_wrapper.py +1158 -123
  83. alita_sdk/tools/figma/figma_client.py +73 -0
  84. alita_sdk/tools/figma/toon_tools.py +2748 -0
  85. alita_sdk/tools/github/__init__.py +2 -1
  86. alita_sdk/tools/github/github_client.py +56 -92
  87. alita_sdk/tools/github/schemas.py +4 -4
  88. alita_sdk/tools/gitlab/__init__.py +2 -1
  89. alita_sdk/tools/gitlab/api_wrapper.py +118 -38
  90. alita_sdk/tools/gitlab_org/__init__.py +2 -1
  91. alita_sdk/tools/gitlab_org/api_wrapper.py +60 -62
  92. alita_sdk/tools/google/bigquery/__init__.py +2 -1
  93. alita_sdk/tools/google_places/__init__.py +2 -1
  94. alita_sdk/tools/jira/__init__.py +2 -1
  95. alita_sdk/tools/keycloak/__init__.py +2 -1
  96. alita_sdk/tools/localgit/__init__.py +2 -1
  97. alita_sdk/tools/memory/__init__.py +1 -1
  98. alita_sdk/tools/ocr/__init__.py +2 -1
  99. alita_sdk/tools/openapi/__init__.py +490 -118
  100. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  101. alita_sdk/tools/openapi/tool.py +20 -0
  102. alita_sdk/tools/pandas/__init__.py +11 -5
  103. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  104. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  105. alita_sdk/tools/postman/__init__.py +2 -1
  106. alita_sdk/tools/pptx/__init__.py +2 -1
  107. alita_sdk/tools/qtest/__init__.py +21 -2
  108. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  109. alita_sdk/tools/rally/__init__.py +2 -1
  110. alita_sdk/tools/rally/api_wrapper.py +1 -1
  111. alita_sdk/tools/report_portal/__init__.py +2 -1
  112. alita_sdk/tools/salesforce/__init__.py +2 -1
  113. alita_sdk/tools/servicenow/__init__.py +11 -10
  114. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  115. alita_sdk/tools/sharepoint/__init__.py +2 -1
  116. alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
  117. alita_sdk/tools/slack/__init__.py +3 -2
  118. alita_sdk/tools/slack/api_wrapper.py +2 -2
  119. alita_sdk/tools/sql/__init__.py +3 -2
  120. alita_sdk/tools/testio/__init__.py +2 -1
  121. alita_sdk/tools/testrail/__init__.py +2 -1
  122. alita_sdk/tools/utils/content_parser.py +77 -3
  123. alita_sdk/tools/utils/text_operations.py +163 -71
  124. alita_sdk/tools/xray/__init__.py +3 -2
  125. alita_sdk/tools/yagmail/__init__.py +2 -1
  126. alita_sdk/tools/zephyr/__init__.py +2 -1
  127. alita_sdk/tools/zephyr_enterprise/__init__.py +2 -1
  128. alita_sdk/tools/zephyr_essential/__init__.py +2 -1
  129. alita_sdk/tools/zephyr_scale/__init__.py +3 -2
  130. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  131. alita_sdk/tools/zephyr_squad/__init__.py +2 -1
  132. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/METADATA +7 -6
  133. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/RECORD +137 -119
  134. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/WHEEL +0 -0
  135. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/entry_points.txt +0 -0
  136. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/licenses/LICENSE +0 -0
  137. {alita_sdk-0.3.532.dist-info → alita_sdk-0.3.602.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,20 @@
1
+ from typing import Optional, Type
2
+
3
+ from pydantic import BaseModel, Field, field_validator
4
+
5
+ from ..base.tool import BaseAction
6
+ from .api_wrapper import OpenApiApiWrapper
7
+
8
+
9
+ class OpenApiAction(BaseAction):
10
+ """Tool for executing a single OpenAPI operation."""
11
+
12
+ api_wrapper: OpenApiApiWrapper = Field(default_factory=OpenApiApiWrapper)
13
+ name: str
14
+ description: str = ""
15
+ args_schema: Optional[Type[BaseModel]] = None
16
+
17
+ @field_validator('name', mode='before')
18
+ @classmethod
19
+ def remove_spaces(cls, v: str) -> str:
20
+ return v.replace(' ', '')
@@ -6,6 +6,7 @@ from pydantic import BaseModel, ConfigDict, create_model, Field
6
6
  from .api_wrapper import PandasWrapper
7
7
  from ..base.tool import BaseAction
8
8
  from ..utils import clean_string, get_max_toolkit_length
9
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
9
10
 
10
11
  name = "pandas"
11
12
 
@@ -29,9 +30,14 @@ class PandasToolkit(BaseToolkit):
29
30
  name,
30
31
  bucket_name=(str, Field(default=None, title="Bucket name", description="Bucket where the content file is stored")),
31
32
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
32
- __config__=ConfigDict(json_schema_extra={'metadata': {"label": "Pandas", "icon_url": "pandas-icon.svg",
33
- "categories": ["analysis"],
34
- "extra_categories": ["data science", "data manipulation", "dataframes"]}})
33
+ __config__=ConfigDict(json_schema_extra={'metadata': {
34
+ "label": "Pandas (Deprecated)",
35
+ "icon_url": "pandas-icon.svg",
36
+ "categories": ["analysis"],
37
+ "deprecated": True,
38
+ "deprecation_message": "This toolkit is deprecated. Use the 'Data Analysis' internal tool instead. Enable it via the 'Internal Tools' menu in chat.",
39
+ "extra_categories": ["data science", "data manipulation", "dataframes"]
40
+ }})
35
41
  )
36
42
 
37
43
  @classmethod
@@ -49,11 +55,11 @@ class PandasToolkit(BaseToolkit):
49
55
  description = f"Toolkit: {toolkit_name}\n{description}"
50
56
  description = description[:1000]
51
57
  tools.append(BaseAction(
52
- api_wrapper=csv_tool_api_wrapper,
58
+ api_wrapper=pandas_api_wrapper,
53
59
  name=tool["name"],
54
60
  description=description,
55
61
  args_schema=tool["args_schema"],
56
- metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
62
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
57
63
  ))
58
64
  return cls(tools=tools)
59
65
 
@@ -158,39 +158,62 @@ class PandasWrapper(BaseToolApiWrapper):
158
158
  f"Retrying Code Generation ({attempts}/{max_retries})..."
159
159
  )
160
160
 
161
- def process_query(self, query: str, filename: str) -> str:
162
- """Analyze and process using query on dataset"""
161
+ def pandas_analyze_data(self, query: str, filename: str) -> str:
162
+ """Analyze data from a file using natural language query.
163
+
164
+ This tool allows you to perform data analysis operations on files using natural language.
165
+ It automatically generates and executes Python pandas code based on your query.
166
+
167
+ Supported file formats: CSV, Excel (.xlsx, .xls), Parquet, JSON, XML, HDF5, Feather, Pickle
168
+
169
+ Parameters:
170
+ query: Natural language description of the analysis to perform. Examples:
171
+ - "Calculate the average sales by region"
172
+ - "Show me a bar chart of products by revenue"
173
+ - "Filter rows where price > 100 and status is 'active'"
174
+ - "What is the correlation between age and income?"
175
+ filename: Name of the file in the artifact bucket (e.g., 'sales_data.csv', 'report.xlsx')
176
+
177
+ Returns:
178
+ Analysis results as text, or confirmation message if a chart was generated and saved.
179
+ Charts are automatically saved to the artifact bucket as PNG files.
180
+
181
+ Examples:
182
+ - pandas_analyze_data(query="Show summary statistics", filename="data.csv")
183
+ - pandas_analyze_data(query="Create a histogram of ages", filename="customers.xlsx")
184
+ - pandas_analyze_data(query="What's the total revenue by month?", filename="sales.parquet")
185
+ """
163
186
  df = self._get_dataframe(filename)
164
187
  code = self.generate_code_with_retries(df, query)
165
- self._log_tool_event(tool_name="process_query",
188
+ self._log_tool_event(tool_name="pandas_analyze_data",
166
189
  message=f"Executing generated code... \n\n```python\n{code}\n```")
167
190
  try:
168
191
  result = self.execute_code(df, code)
169
192
  except Exception as e:
170
193
  logger.error(f"Code execution failed: {format_exc()}")
171
- self._log_tool_event(tool_name="process_query",
172
- message=f"Executing generated code... \n\n```python\n{code}\n```")
173
194
  raise
174
- self._log_tool_event(tool_name="process_query",
175
- message=f"Executing generated code... \n\n```python\n{code}\n```")
176
195
  if result.get("df") is not None:
177
196
  df = result.pop("df")
178
197
  # Not saving dataframe to artifact repo for now
179
198
  # self._save_dataframe(df, filename)
180
199
  if result.get('chart'):
200
+ chart_results = []
181
201
  if isinstance(result['chart'], list):
182
202
  for ind, chart in enumerate(result['chart']):
183
203
  chart_filename = f"chart_{uuid4()}.png"
184
204
  chart_data = base64.b64decode(chart)
185
205
  self.alita.create_artifact(self.bucket_name, chart_filename, chart_data)
186
- result['result'] = f"Chart #{ind} saved to {chart_filename}"
206
+ chart_url = f"{self.alita.base_url}/api/v1/artifacts/artifact/default/{self.alita.project_id}/{self.bucket_name}/{chart_filename}"
207
+ chart_results.append(f"Chart #{ind+1} saved and available at: {chart_url}")
208
+ result['result'] = "\n".join(chart_results)
187
209
  else:
188
210
  # Handle single chart case (not in a list)
189
211
  chart = result['chart']
190
212
  chart_filename = f"chart_{uuid4()}.png"
191
213
  chart_data = base64.b64decode(chart)
192
214
  self.alita.create_artifact(self.bucket_name, chart_filename, chart_data)
193
- result['result'] = f"Chart saved to {chart_filename}"
215
+ chart_url = f"{self.alita.base_url}/api/v1/artifacts/artifact/default/{self.alita.project_id}/{self.bucket_name}/{chart_filename}"
216
+ result['result'] = f"Chart saved and available at: {chart_url}\n\nYou can embed this image in your response using markdown: ![Chart]({chart_url})"
194
217
  return result.get("result", None)
195
218
 
196
219
  def save_dataframe(self, source_df: str, target_file: str) -> str:
@@ -253,23 +276,13 @@ class PandasWrapper(BaseToolApiWrapper):
253
276
  def get_available_tools(self):
254
277
  return [
255
278
  {
256
- "name": "process_query",
257
- "ref": self.process_query,
258
- "description": self.process_query.__doc__,
259
- "args_schema": create_model(
260
- "ProcessQueryModel",
261
- query=(str, Field(description="Task to solve")),
262
- filename=(str, Field(description="File to be processed"))
263
- )
264
- },
265
- {
266
- "name": "save_dataframe",
267
- "ref": self.save_dataframe,
268
- "description": self.save_dataframe.__doc__,
279
+ "name": "pandas_analyze_data",
280
+ "ref": self.pandas_analyze_data,
281
+ "description": self.pandas_analyze_data.__doc__,
269
282
  "args_schema": create_model(
270
- "SaveDataFrameModel",
271
- source_df=(str, Field(description="Source dataframe file to be saved")),
272
- target_file=(str, Field(description="Target filename with extension for saving"))
283
+ "AnalyseDataModel",
284
+ query=(str, Field(description="Natural language query describing what analysis to perform on the data")),
285
+ filename=(str, Field(description="Name of the file to analyze (e.g., 'data.csv', 'report.xlsx')"))
273
286
  )
274
287
  }
275
288
  ]
@@ -39,7 +39,9 @@ class CodeGenerator:
39
39
  {"role": "user", "content": [{"type": "text", "text": prompt}]}
40
40
  ]
41
41
  # Generate the code
42
- code = self.llm.invoke(messages).content
42
+ from alita_sdk.runtime.langchain.utils import extract_text_from_completion
43
+ completion = self.llm.invoke(messages)
44
+ code = extract_text_from_completion(completion)
43
45
  return self.validate_and_clean_code(code)
44
46
 
45
47
  except Exception as e:
@@ -8,6 +8,7 @@ from ..base.tool import BaseAction
8
8
  from .api_wrapper import PostmanApiWrapper
9
9
  from ..utils import clean_string, get_max_toolkit_length, check_connection_response
10
10
  from ...configurations.postman import PostmanConfiguration
11
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
11
12
 
12
13
  name = "postman"
13
14
 
@@ -102,7 +103,7 @@ class PostmanToolkit(BaseToolkit):
102
103
  mode=tool["mode"],
103
104
  description=description,
104
105
  args_schema=tool["args_schema"],
105
- metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
106
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
106
107
  ))
107
108
  return cls(tools=tools)
108
109
 
@@ -8,6 +8,7 @@ from .pptx_wrapper import PPTXWrapper
8
8
 
9
9
  from ..base.tool import BaseAction
10
10
  from ..utils import clean_string, get_max_toolkit_length
11
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -87,7 +88,7 @@ class PPTXToolkit(BaseToolkit):
87
88
  name=tool["name"],
88
89
  description=description,
89
90
  args_schema=tool["args_schema"],
90
- metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
91
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
91
92
  ))
92
93
 
93
94
  return cls(tools=tools)
@@ -9,6 +9,8 @@ from .tool import QtestAction
9
9
  from ..elitea_base import filter_missconfigured_index_tools
10
10
  from ..utils import clean_string, get_max_toolkit_length, check_connection_response
11
11
  from ...configurations.qtest import QtestConfiguration
12
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
13
+ from ...configurations.pgvector import PgVectorConfiguration
12
14
 
13
15
  name = "qtest"
14
16
 
@@ -20,7 +22,14 @@ def get_tools(tool):
20
22
  no_of_tests_shown_in_dql_search=tool['settings'].get('no_of_tests_shown_in_dql_search'),
21
23
  qtest_configuration=tool['settings']['qtest_configuration'],
22
24
  toolkit_name=tool.get('toolkit_name'),
23
- llm=tool['settings'].get('llm', None)
25
+ llm=tool['settings'].get('llm', None),
26
+ alita=tool['settings'].get('alita', None),
27
+
28
+ # indexer settings
29
+ pgvector_configuration=tool['settings'].get('pgvector_configuration', {}),
30
+ collection_name=str(tool.get('toolkit_name', '')),
31
+ embedding_model=tool['settings'].get('embedding_model', None),
32
+ vectorstore_type="PGVector"
24
33
  )
25
34
  return toolkit.tools
26
35
 
@@ -38,6 +47,15 @@ class QtestToolkit(BaseToolkit):
38
47
  qtest_project_id=(int, Field(description="QTest project id")),
39
48
  no_of_tests_shown_in_dql_search=(Optional[int], Field(description="Max number of items returned by dql search",
40
49
  default=10)),
50
+ # indexer configuration
51
+ pgvector_configuration=(Optional[PgVectorConfiguration], Field(
52
+ default=None,
53
+ description="PgVector Configuration for indexing",
54
+ json_schema_extra={'configuration_types': ['pgvector']})),
55
+ embedding_model=(Optional[str], Field(
56
+ default=None,
57
+ description="Embedding model configuration for indexing",
58
+ json_schema_extra={'configuration_model': 'embedding'})),
41
59
 
42
60
  selected_tools=(List[Literal[tuple(selected_tools)]],
43
61
  Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
@@ -71,6 +89,7 @@ class QtestToolkit(BaseToolkit):
71
89
  **kwargs,
72
90
  # TODO use qtest_configuration fields
73
91
  **kwargs['qtest_configuration'],
92
+ **(kwargs.get('pgvector_configuration') or {}),
74
93
  }
75
94
  qtest_api_wrapper = QtestApiWrapper(**wrapper_payload)
76
95
  available_tools = qtest_api_wrapper.get_available_tools()
@@ -89,7 +108,7 @@ class QtestToolkit(BaseToolkit):
89
108
  mode=tool["mode"],
90
109
  description=description,
91
110
  args_schema=tool["args_schema"],
92
- metadata={"toolkit_name": toolkit_name} if toolkit_name else {}
111
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
93
112
  ))
94
113
  return cls(tools=tools)
95
114