alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,9 @@ from ..base.tool import BaseAction
5
5
  from pydantic import create_model, BaseModel, ConfigDict, Field
6
6
 
7
7
  from ..elitea_base import filter_missconfigured_index_tools
8
- from ..utils import clean_string, TOOLKIT_SPLITTER,get_max_toolkit_length
8
+ from ..utils import clean_string, get_max_toolkit_length
9
9
  from ...configurations.salesforce import SalesforceConfiguration
10
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
10
11
 
11
12
  name = "salesforce"
12
13
 
@@ -19,11 +20,9 @@ def get_tools(tool):
19
20
 
20
21
  class SalesforceToolkit(BaseToolkit):
21
22
  tools: List[BaseTool] = []
22
- toolkit_max_length: int = 0
23
23
  @staticmethod
24
24
  def toolkit_config_schema() -> BaseModel:
25
25
  available_tools = {x['name']: x['args_schema'].schema() for x in SalesforceApiWrapper.model_construct().get_available_tools()}
26
- SalesforceToolkit.toolkit_max_length = get_max_toolkit_length(available_tools)
27
26
  return create_model(
28
27
  name,
29
28
  api_version=(str, Field(description="Salesforce API Version", default='v59.0')),
@@ -31,7 +30,6 @@ class SalesforceToolkit(BaseToolkit):
31
30
  selected_tools=(List[Literal[tuple(available_tools)]], Field(default=[], json_schema_extra={'args_schemas': available_tools})),
32
31
  __config__=ConfigDict(json_schema_extra={'metadata': {
33
32
  "label": "Salesforce", "icon_url": "salesforce-icon.svg",
34
- "max_length": SalesforceToolkit.toolkit_max_length,
35
33
  "categories": ["other"],
36
34
  "extra_categories": ["customer relationship management", "cloud computing", "marketing automation", "salesforce"]
37
35
  }})
@@ -48,18 +46,21 @@ class SalesforceToolkit(BaseToolkit):
48
46
  **kwargs.get('salesforce_configuration', {}),
49
47
  }
50
48
  api_wrapper = SalesforceApiWrapper(**wrapper_payload)
51
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
52
49
  tools = []
53
50
 
54
51
  for tool in api_wrapper.get_available_tools():
55
52
  if selected_tools and tool["name"] not in selected_tools:
56
53
  continue
57
-
54
+ description = f"Salesforce Tool: {tool['description']}"
55
+ if toolkit_name:
56
+ description = f"{description}\nToolkit: {toolkit_name}"
57
+ description = description[:1000]
58
58
  tools.append(BaseAction(
59
59
  api_wrapper=api_wrapper,
60
- name=prefix + tool["name"],
61
- description=f"Salesforce Tool: {tool['description']}",
62
- args_schema=tool["args_schema"]
60
+ name=tool["name"],
61
+ description=description,
62
+ args_schema=tool["args_schema"],
63
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
63
64
  ))
64
65
 
65
66
  return cls(tools=tools)
@@ -7,35 +7,33 @@ from ..base.tool import BaseAction
7
7
  from pydantic import create_model, BaseModel, ConfigDict, Field
8
8
 
9
9
  from ..elitea_base import filter_missconfigured_index_tools
10
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
11
10
  from ...configurations.service_now import ServiceNowConfiguration
11
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
12
12
 
13
13
 
14
14
  name = "service_now"
15
15
 
16
16
  def get_tools(tool):
17
+ settings = tool.get('settings') or {}
18
+
17
19
  return ServiceNowToolkit().get_toolkit(
18
- selected_tools=tool['settings'].get('selected_tools', []),
19
- instance_alias=tool['settings'].get('instance_alias', None),
20
- base_url=tool['settings']['base_url'],
21
- servicenow_configuration=tool['settings']['servicenow_configuration'],
22
- response_fields=tool['settings'].get('response_fields', None),
20
+ selected_tools=settings.get('selected_tools', []),
21
+ instance_alias=settings.get('instance_alias', None),
22
+ servicenow_configuration=settings.get('servicenow_configuration', None),
23
+ response_fields=settings.get('response_fields', None),
23
24
  toolkit_name=tool.get('toolkit_name')
24
25
  ).get_tools()
25
26
 
26
27
 
27
28
  class ServiceNowToolkit(BaseToolkit):
28
29
  tools: List[BaseTool] = []
29
- toolkit_max_length: int = 0
30
30
 
31
31
  @staticmethod
32
32
  def toolkit_config_schema() -> BaseModel:
33
33
  selected_tools = {x['name']: x['args_schema'].schema() for x in
34
34
  ServiceNowAPIWrapper.model_construct().get_available_tools()}
35
- ServiceNowToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
36
35
  return create_model(
37
36
  name,
38
- name=(str, Field(description="Toolkit name")),
39
37
  response_fields=(Optional[str], Field(description="Response fields", default=None)),
40
38
  servicenow_configuration=(ServiceNowConfiguration, Field(description="ServiceNow Configuration",
41
39
  json_schema_extra={
@@ -47,7 +45,6 @@ class ServiceNowToolkit(BaseToolkit):
47
45
  'metadata': {
48
46
  "label": "ServiceNow",
49
47
  "icon_url": "service-now.svg",
50
- "max_length": ServiceNowToolkit.toolkit_max_length,
51
48
  "hidden": False,
52
49
  "sections": {
53
50
  "auth": {
@@ -79,18 +76,24 @@ class ServiceNowToolkit(BaseToolkit):
79
76
  **kwargs['servicenow_configuration'],
80
77
  }
81
78
  servicenow_api_wrapper = ServiceNowAPIWrapper(**wrapper_payload)
82
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
83
79
  available_tools = servicenow_api_wrapper.get_available_tools()
84
80
  tools = []
85
81
  for tool in available_tools:
86
82
  if selected_tools:
87
83
  if tool["name"] not in selected_tools:
88
84
  continue
85
+ base_url = getattr(servicenow_api_wrapper, "base_url", "") or ""
86
+ description = tool.get("description", "") if isinstance(tool, dict) else ""
87
+ if toolkit_name:
88
+ description = f"Toolkit: {toolkit_name}\n{description}"
89
+ description = f"ServiceNow: {base_url}\n{description}".strip()
90
+ description = description[:1000]
89
91
  tools.append(BaseAction(
90
92
  api_wrapper=servicenow_api_wrapper,
91
- name=prefix + tool["name"],
92
- description=f"ServiceNow: {servicenow_api_wrapper.base_url} " + tool["description"],
93
- args_schema=tool["args_schema"]
93
+ name=tool["name"],
94
+ description=description,
95
+ args_schema=tool["args_schema"],
96
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
94
97
  ))
95
98
  return cls(tools=tools)
96
99
 
@@ -58,7 +58,7 @@ class ServiceNowAPIWrapper(BaseToolApiWrapper):
58
58
  password = SecretStr(values['password'])
59
59
  username = values['username']
60
60
  cls.fields = values.get('fields', ['sys_id', 'number', 'state', 'short_description', 'description', 'priority', 'category', 'urgency', 'impact', 'creation_date'])
61
- cls.client = ServiceNowClient(base_url, (username, password.get_secret_value()))
61
+ cls.client = ServiceNowClient(instance=base_url, auth=(username, password.get_secret_value()))
62
62
  return values
63
63
 
64
64
  def get_incidents(self, data: Optional[Dict[str, Any]] = None) -> ToolException | str:
@@ -5,9 +5,10 @@ from pydantic import create_model, BaseModel, ConfigDict, Field
5
5
  from .api_wrapper import SharepointApiWrapper
6
6
  from ..base.tool import BaseAction
7
7
  from ..elitea_base import filter_missconfigured_index_tools
8
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
8
+ from ..utils import clean_string, get_max_toolkit_length
9
9
  from ...configurations.pgvector import PgVectorConfiguration
10
10
  from ...configurations.sharepoint import SharepointConfiguration
11
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
11
12
 
12
13
  name = "sharepoint"
13
14
 
@@ -29,12 +30,10 @@ def get_tools(tool):
29
30
 
30
31
  class SharepointToolkit(BaseToolkit):
31
32
  tools: List[BaseTool] = []
32
- toolkit_max_length: int = 0
33
33
 
34
34
  @staticmethod
35
35
  def toolkit_config_schema() -> BaseModel:
36
36
  selected_tools = {x['name']: x['args_schema'].schema() for x in SharepointApiWrapper.model_construct().get_available_tools()}
37
- SharepointToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
38
37
  return create_model(
39
38
  name,
40
39
  sharepoint_configuration=(SharepointConfiguration, Field(description="SharePoint Configuration", json_schema_extra={'configuration_types': ['sharepoint']})),
@@ -48,7 +47,6 @@ class SharepointToolkit(BaseToolkit):
48
47
  __config__=ConfigDict(json_schema_extra={
49
48
  'metadata': {
50
49
  "label": "Sharepoint", "icon_url": "sharepoint.svg",
51
- "max_length": SharepointToolkit.toolkit_max_length,
52
50
  "categories": ["office"],
53
51
  "extra_categories": ["microsoft", "cloud storage", "team collaboration", "content management"]
54
52
  }})
@@ -65,18 +63,22 @@ class SharepointToolkit(BaseToolkit):
65
63
  **(kwargs.get('pgvector_configuration') or {}),
66
64
  }
67
65
  sharepoint_api_wrapper = SharepointApiWrapper(**wrapper_payload)
68
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
69
66
  available_tools = sharepoint_api_wrapper.get_available_tools()
70
67
  tools = []
71
68
  for tool in available_tools:
72
69
  if selected_tools:
73
70
  if tool["name"] not in selected_tools:
74
71
  continue
72
+ description = f"Sharepoint {sharepoint_api_wrapper.site_url}\n{tool['description']}"
73
+ if toolkit_name:
74
+ description = f"{description}\nToolkit: {toolkit_name}"
75
+ description = description[:1000]
75
76
  tools.append(BaseAction(
76
77
  api_wrapper=sharepoint_api_wrapper,
77
- name=prefix + tool["name"],
78
- description=f"Sharepoint {sharepoint_api_wrapper.site_url}\n{tool['description']}",
79
- args_schema=tool["args_schema"]
78
+ name=tool["name"],
79
+ description=description,
80
+ args_schema=tool["args_schema"],
81
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
80
82
  ))
81
83
  return cls(tools=tools)
82
84
 
@@ -20,7 +20,7 @@ NoInput = create_model(
20
20
  ReadList = create_model(
21
21
  "ReadList",
22
22
  list_title=(str, Field(description="Name of a Sharepoint list to be read.")),
23
- limit=(Optional[int], Field(description="Limit (maximum number) of list items to be returned", default=1000))
23
+ limit=(Optional[int], Field(description="Limit (maximum number) of list items to be returned", default=1000, gt=0))
24
24
  )
25
25
 
26
26
  GetFiles = create_model(
@@ -30,7 +30,7 @@ GetFiles = create_model(
30
30
  "Can be called with synonyms, such as First, Top, etc., "
31
31
  "or can be reflected just by a number for example 'Top 10 files'. "
32
32
  "Use default value if not specified in a query WITH NO EXTRA "
33
- "CONFIRMATION FROM A USER", default=100)),
33
+ "CONFIRMATION FROM A USER", default=100, gt=0)),
34
34
  )
35
35
 
36
36
  ReadDocument = create_model(
@@ -271,13 +271,13 @@ class SharepointApiWrapper(NonCodeIndexerToolkit):
271
271
  file_name = file.get('Name', '')
272
272
 
273
273
  # Check if file should be skipped based on skip_extensions
274
- if any(re.match(pattern.replace('*', '.*') + '$', file_name, re.IGNORECASE)
274
+ if any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
275
275
  for pattern in skip_extensions):
276
276
  continue
277
277
 
278
278
  # Check if file should be included based on include_extensions
279
279
  # If include_extensions is empty, process all files (that weren't skipped)
280
- if include_extensions and not (any(re.match(pattern.replace('*', '.*') + '$', file_name, re.IGNORECASE)
280
+ if include_extensions and not (any(re.match(re.escape(pattern).replace(r'\*', '.*') + '$', file_name, re.IGNORECASE)
281
281
  for pattern in include_extensions)):
282
282
  continue
283
283
 
@@ -12,9 +12,10 @@ from pydantic import create_model, BaseModel, Field
12
12
  from ..base.tool import BaseAction
13
13
 
14
14
  from .api_wrapper import SlackApiWrapper
15
- from ..utils import TOOLKIT_SPLITTER, clean_string, get_max_toolkit_length, check_connection_response
15
+ from ..utils import clean_string, get_max_toolkit_length, check_connection_response
16
16
  from slack_sdk.errors import SlackApiError
17
17
  from slack_sdk import WebClient
18
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
18
19
 
19
20
  name = "slack"
20
21
 
@@ -28,12 +29,10 @@ def get_tools(tool):
28
29
 
29
30
  class SlackToolkit(BaseToolkit):
30
31
  tools: List[BaseTool] = []
31
- toolkit_max_length: int = 0
32
32
 
33
33
  @staticmethod
34
34
  def toolkit_config_schema() -> BaseModel:
35
35
  selected_tools = {x['name']: x['args_schema'].schema() for x in SlackApiWrapper.model_construct().get_available_tools()}
36
- SlackToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
37
36
 
38
37
  @check_connection_response
39
38
  def check_connection(self):
@@ -59,7 +58,6 @@ class SlackToolkit(BaseToolkit):
59
58
  'metadata': {
60
59
  "label": "Slack",
61
60
  "icon_url": "slack-icon.svg",
62
- "max_length": SlackToolkit.toolkit_max_length,
63
61
  "categories": ["communication"],
64
62
  "extra_categories": ["slack", "chat", "messaging", "collaboration"],
65
63
  }
@@ -79,17 +77,21 @@ class SlackToolkit(BaseToolkit):
79
77
  **kwargs['slack_configuration'],
80
78
  }
81
79
  slack_api_wrapper = SlackApiWrapper(**wrapper_payload)
82
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
83
80
  available_tools = slack_api_wrapper.get_available_tools()
84
81
  tools = []
85
82
  for tool in available_tools:
86
83
  if selected_tools and tool["name"] not in selected_tools:
87
84
  continue
88
- tools.append(BaseAction(
85
+ description = f"Slack Tool: {tool['description']}"
86
+ if toolkit_name:
87
+ description = f"{description}\nToolkit: {toolkit_name}"
88
+ description = description[:1000]
89
+ tools.append(BaseAction(
89
90
  api_wrapper=slack_api_wrapper,
90
- name=prefix + tool["name"],
91
- description=f"Slack Tool: {tool['description']}",
91
+ name=tool["name"],
92
+ description=description,
92
93
  args_schema=tool["args_schema"],
94
+ 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
95
  ))
94
96
  return cls(tools=tools)
95
97
 
@@ -17,8 +17,8 @@ SendMessageModel = create_model(
17
17
 
18
18
  ReadMessagesModel = create_model(
19
19
  "ReadMessagesModel",
20
- channel_id=(Optional[str], Field(default=None,description="Channel ID, user ID, or conversation ID to read messages from. (like C12345678 for public channels, D12345678 for DMs)")),
21
- limit=(int, Field(default=10, description="The number of messages to fetch (default is 10)."))
20
+ channel_id=(Optional[str], Field(default=None,description="Channel ID, user ID, or conversation ID to read messages from. (like C12345678 for public channels, D12345678 for DMs)")),
21
+ limit=(int, Field(default=10, description="The number of messages to fetch (default is 10).", gt=0))
22
22
  )
23
23
 
24
24
  CreateChannelModel = create_model(
@@ -7,8 +7,9 @@ from .api_wrapper import SQLApiWrapper
7
7
  from ..base.tool import BaseAction
8
8
  from .models import SQLDialect
9
9
  from ..elitea_base import filter_missconfigured_index_tools
10
- from ..utils import TOOLKIT_SPLITTER, clean_string, get_max_toolkit_length
10
+ from ..utils import clean_string, get_max_toolkit_length
11
11
  from ...configurations.sql import SqlConfiguration
12
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
12
13
 
13
14
  name = "sql"
14
15
 
@@ -24,16 +25,14 @@ def get_tools(tool):
24
25
 
25
26
  class SQLToolkit(BaseToolkit):
26
27
  tools: list[BaseTool] = []
27
- toolkit_max_length: int = 0
28
28
 
29
29
  @staticmethod
30
30
  def toolkit_config_schema() -> BaseModel:
31
31
  selected_tools = {x['name']: x['args_schema'].schema() for x in SQLApiWrapper.model_construct().get_available_tools()}
32
- SQLToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
33
32
  supported_dialects = (d.value for d in SQLDialect)
34
33
  return create_model(
35
34
  name,
36
- dialect=(Literal[tuple(supported_dialects)], Field(description="Database dialect (mysql or postgres)")),
35
+ dialect=(Literal[tuple(supported_dialects)], Field(default=SQLDialect.POSTGRES.value, description="Database dialect (mysql or postgres)")),
37
36
  database_name=(str, Field(description="Database name")),
38
37
  sql_configuration=(SqlConfiguration, Field(description="SQL Configuration", json_schema_extra={'configuration_types': ['sql']})),
39
38
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
@@ -42,7 +41,6 @@ class SQLToolkit(BaseToolkit):
42
41
  'metadata':
43
42
  {
44
43
  "label": "SQL", "icon_url": "sql-icon.svg",
45
- "max_length": SQLToolkit.toolkit_max_length,
46
44
  "categories": ["development"],
47
45
  "extra_categories": ["sql", "data management", "data analysis"]}})
48
46
  )
@@ -57,17 +55,21 @@ class SQLToolkit(BaseToolkit):
57
55
  **kwargs.get('sql_configuration', {}),
58
56
  }
59
57
  sql_api_wrapper = SQLApiWrapper(**wrapper_payload)
60
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
61
58
  available_tools = sql_api_wrapper.get_available_tools()
62
59
  tools = []
63
60
  for tool in available_tools:
64
61
  if selected_tools and tool["name"] not in selected_tools:
65
62
  continue
63
+ description = f"{tool['description']}\nDatabase: {sql_api_wrapper.database_name}. Host: {sql_api_wrapper.host}"
64
+ if toolkit_name:
65
+ description = f"{description}\nToolkit: {toolkit_name}"
66
+ description = description[:1000]
66
67
  tools.append(BaseAction(
67
68
  api_wrapper=sql_api_wrapper,
68
- name=prefix + tool["name"],
69
- description=f"{tool['description']}\nDatabase: {sql_api_wrapper.database_name}. Host: {sql_api_wrapper.host}",
70
- args_schema=tool["args_schema"]
69
+ name=tool["name"],
70
+ description=description,
71
+ args_schema=tool["args_schema"],
72
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
71
73
  ))
72
74
  return cls(tools=tools)
73
75
 
@@ -6,8 +6,9 @@ from pydantic import create_model, BaseModel, ConfigDict, Field
6
6
  from .api_wrapper import TestIOApiWrapper
7
7
  from ..base.tool import BaseAction
8
8
  from ..elitea_base import filter_missconfigured_index_tools
9
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
9
+ from ..utils import clean_string, get_max_toolkit_length
10
10
  from ...configurations.testio import TestIOConfiguration
11
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
11
12
 
12
13
  name = "testio"
13
14
 
@@ -19,8 +20,6 @@ def get_tools(tool):
19
20
  ).get_tools()
20
21
 
21
22
 
22
- TOOLKIT_MAX_LENGTH = 25
23
-
24
23
  class TestIOToolkit(BaseToolkit):
25
24
  tools: list[BaseTool] = []
26
25
 
@@ -33,7 +32,6 @@ class TestIOToolkit(BaseToolkit):
33
32
  testio_configuration=(TestIOConfiguration, Field(description="TestIO Configuration", json_schema_extra={'configuration_types': ['testio']})),
34
33
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
35
34
  __config__=ConfigDict(json_schema_extra={'metadata': {"label": "TestIO", "icon_url": "testio-icon.svg",
36
- "max_length": TOOLKIT_MAX_LENGTH,
37
35
  "categories": ["testing"],
38
36
  "extra_categories": ["test automation", "test case management", "test planning"]}})
39
37
  )
@@ -48,17 +46,21 @@ class TestIOToolkit(BaseToolkit):
48
46
  **kwargs.get('testio_configuration', {}),
49
47
  }
50
48
  testio_api_wrapper = TestIOApiWrapper(**wrapper_payload)
51
- prefix = clean_string(toolkit_name, TOOLKIT_MAX_LENGTH) + TOOLKIT_SPLITTER if toolkit_name else ''
52
49
  available_tools = testio_api_wrapper.get_available_tools()
53
50
  tools = []
54
51
  for tool in available_tools:
55
52
  if selected_tools and tool["name"] not in selected_tools:
56
53
  continue
54
+ description = tool["description"]
55
+ if toolkit_name:
56
+ description = f"Toolkit: {toolkit_name}\n{description}"
57
+ description = description[:1000]
57
58
  tools.append(BaseAction(
58
59
  api_wrapper=testio_api_wrapper,
59
- name=prefix + tool["name"],
60
- description=tool["description"],
61
- args_schema=tool["args_schema"]
60
+ name=tool["name"],
61
+ description=description,
62
+ args_schema=tool["args_schema"],
63
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
62
64
  ))
63
65
  return cls(tools=tools)
64
66
 
@@ -7,9 +7,10 @@ import requests
7
7
  from .api_wrapper import TestrailAPIWrapper
8
8
  from ..base.tool import BaseAction
9
9
  from ..elitea_base import filter_missconfigured_index_tools
10
- from ..utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length, check_connection_response
10
+ from ..utils import clean_string, get_max_toolkit_length, check_connection_response
11
11
  from ...configurations.testrail import TestRailConfiguration
12
12
  from ...configurations.pgvector import PgVectorConfiguration
13
+ from ...runtime.utils.constants import TOOLKIT_NAME_META, TOOL_NAME_META, TOOLKIT_TYPE_META
13
14
 
14
15
  name = "testrail"
15
16
 
@@ -31,12 +32,10 @@ def get_tools(tool):
31
32
 
32
33
  class TestrailToolkit(BaseToolkit):
33
34
  tools: List[BaseTool] = []
34
- toolkit_max_length: int = 0
35
35
 
36
36
  @staticmethod
37
37
  def toolkit_config_schema() -> BaseModel:
38
38
  selected_tools = {x['name']: x['args_schema'].schema() for x in TestrailAPIWrapper.model_construct().get_available_tools()}
39
- TestrailToolkit.toolkit_max_length = get_max_toolkit_length(selected_tools)
40
39
  m = create_model(
41
40
  name,
42
41
  testrail_configuration=(Optional[TestRailConfiguration], Field(description="TestRail Configuration", json_schema_extra={'configuration_types': ['testrail']})),
@@ -47,7 +46,6 @@ class TestrailToolkit(BaseToolkit):
47
46
  selected_tools=(List[Literal[tuple(selected_tools)]], Field(default=[], json_schema_extra={'args_schemas': selected_tools})),
48
47
  __config__=ConfigDict(json_schema_extra={'metadata':
49
48
  {"label": "Testrail", "icon_url": "testrail-icon.svg",
50
- "max_length": TestrailToolkit.toolkit_max_length,
51
49
  "categories": ["test management"],
52
50
  "extra_categories": ["quality assurance", "test case management", "test planning"]
53
51
  }})
@@ -77,18 +75,23 @@ class TestrailToolkit(BaseToolkit):
77
75
  **(kwargs.get('pgvector_configuration') or {}),
78
76
  }
79
77
  testrail_api_wrapper = TestrailAPIWrapper(**wrapper_payload)
80
- prefix = clean_string(toolkit_name, cls.toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
81
78
  available_tools = testrail_api_wrapper.get_available_tools()
82
79
  tools = []
83
80
  for tool in available_tools:
84
81
  if selected_tools:
85
82
  if tool["name"] not in selected_tools:
86
83
  continue
84
+ description = tool["description"]
85
+ if toolkit_name:
86
+ description = f"Toolkit: {toolkit_name}\n{description}"
87
+ description = description + "\nTestrail instance: " + testrail_api_wrapper.url
88
+ description = description[:1000]
87
89
  tools.append(BaseAction(
88
90
  api_wrapper=testrail_api_wrapper,
89
- name=prefix + tool["name"],
90
- description=tool["description"] + "\nTestrail instance: " + testrail_api_wrapper.url,
91
- args_schema=tool["args_schema"]
91
+ name=tool["name"],
92
+ description=description,
93
+ args_schema=tool["args_schema"],
94
+ metadata={TOOLKIT_NAME_META: toolkit_name, TOOLKIT_TYPE_META: name, TOOL_NAME_META: tool["name"]} if toolkit_name else {TOOL_NAME_META: tool["name"]}
92
95
  ))
93
96
  return cls(tools=tools)
94
97
 
@@ -697,7 +697,7 @@ class TestrailAPIWrapper(NonCodeIndexerToolkit):
697
697
  'id': str(case.get('id', '')),
698
698
  IndexerKeywords.UPDATED_ON.value: case.get('updated_on') or -1,
699
699
  'labels': [lbl['title'] for lbl in case.get('labels', [])],
700
- 'type': case.get('type_id') or -1,
700
+ 'type': "testrail_test_case",
701
701
  'priority': case.get('priority_id') or -1,
702
702
  'milestone': case.get('milestone_id') or -1,
703
703
  'estimate': case.get('estimate') or '',
@@ -7,6 +7,8 @@ import requests
7
7
  from pydantic import create_model, Field
8
8
 
9
9
 
10
+ # DEPRECATED: Tool names no longer use prefixes
11
+ # Kept for backward compatibility only
10
12
  TOOLKIT_SPLITTER = "___"
11
13
  TOOL_NAME_LIMIT = 64
12
14
 
@@ -22,10 +24,13 @@ def clean_string(s: str, max_length: int = 0):
22
24
 
23
25
 
24
26
  def get_max_toolkit_length(selected_tools: Any):
25
- """Calculates the maximum length of the toolkit name based on the selected tools per toolkit."""
26
-
27
- longest_tool_name_length = max(len(tool_name) for tool_name in selected_tools.keys())
28
- return TOOL_NAME_LIMIT - longest_tool_name_length - len(TOOLKIT_SPLITTER)
27
+ """DEPRECATED: Calculates the maximum length of the toolkit name.
28
+
29
+ This function is deprecated as tool names no longer use prefixes.
30
+ Returns a fixed value for backward compatibility.
31
+ """
32
+ # Return a reasonable default since we no longer use prefixes
33
+ return 50
29
34
 
30
35
 
31
36
  def parse_list(list_str: str = None) -> List[str]:
@@ -109,7 +109,15 @@ def parse_file_content(file_name=None, file_content=None, is_capture_image: bool
109
109
  loader_extra_config=loader_kwargs,
110
110
  llm=llm)
111
111
  except Exception as e:
112
- return ToolException(f"Error reading file ({file_name or file_path}) content. Make sure these types are supported: {str(e)}")
112
+ # Surface full underlying error message (including nested causes) so that
113
+ # JSONDecodeError or other specific issues are not hidden behind
114
+ # generic RuntimeError messages from loaders.
115
+ root_msg = str(e)
116
+ if getattr(e, "__cause__", None):
117
+ root_msg = f"{root_msg} | Cause: {e.__cause__}"
118
+ return ToolException(
119
+ f"Error reading file ({file_name or file_path}) content. Make sure these types are supported: {root_msg}"
120
+ )
113
121
 
114
122
  def load_file_docs(file_name=None, file_content=None, is_capture_image: bool = False, page_number: int = None,
115
123
  sheet_name: str = None, llm=None, file_path: str = None, excel_by_sheets: bool = False) -> List[Document] | ToolException:
@@ -130,7 +138,38 @@ def load_file_docs(file_name=None, file_content=None, is_capture_image: bool = F
130
138
 
131
139
  def get_loader_kwargs(loader_object, file_name=None, file_content=None, is_capture_image: bool = False, page_number: int = None,
132
140
  sheet_name: str = None, llm=None, file_path: str = None, excel_by_sheets: bool = False, prompt=None):
133
- loader_kwargs = deepcopy(loader_object['kwargs'])
141
+ """Build loader kwargs safely without deepcopying non-picklable objects like LLMs.
142
+
143
+ We avoid copying keys that are going to be overridden by this function anyway
144
+ (file_path, file_content, file_name, extract_images, llm, page_number,
145
+ sheet_name, excel_by_sheets, prompt, row_content, json_documents) to
146
+ prevent errors such as `cannot pickle '_thread.RLock' object` when an LLM
147
+ or client with internal locks is stored in the original kwargs.
148
+ """
149
+ if not loader_object:
150
+ raise ToolException("Loader configuration is missing.")
151
+
152
+ original_kwargs = loader_object.get("kwargs", {}) or {}
153
+
154
+ # Keys that will be overwritten below – skip them when copying
155
+ overridden_keys = {
156
+ "file_path",
157
+ "file_content",
158
+ "file_name",
159
+ "extract_images",
160
+ "llm",
161
+ "page_number",
162
+ "sheet_name",
163
+ "excel_by_sheets",
164
+ "prompt",
165
+ "row_content",
166
+ "json_documents",
167
+ }
168
+
169
+ # Build a safe shallow copy without overridden keys to avoid deepcopy
170
+ # of potentially non-picklable objects (e.g., llm with internal RLock).
171
+ loader_kwargs = {k: v for k, v in original_kwargs.items() if k not in overridden_keys}
172
+
134
173
  loader_kwargs.update({
135
174
  "file_path": file_path,
136
175
  "file_content": file_content,
@@ -212,6 +251,41 @@ def load_content_from_bytes(file_content: bytes, extension: str = None, loader_e
212
251
  if temp_file_path and os.path.exists(temp_file_path):
213
252
  os.remove(temp_file_path)
214
253
 
254
+
255
+ def _load_content_from_bytes_with_prompt(file_content: bytes, extension: str = None, loader_extra_config: dict = None, llm = None, prompt: str = image_processing_prompt) -> str:
256
+ """Internal helper that behaves like load_content_from_bytes but also propagates prompt.
257
+
258
+ This keeps the public load_content_from_bytes API unchanged while allowing newer
259
+ code paths to pass an explicit prompt through to the loader.
260
+ """
261
+ temp_file_path = None
262
+ try:
263
+ with tempfile.NamedTemporaryFile(mode='w+b', delete=False, suffix=extension or '') as temp_file:
264
+ temp_file.write(file_content)
265
+ temp_file.flush()
266
+ temp_file_path = temp_file.name
267
+
268
+ # Use prepare_loader so that prompt and other kwargs are handled consistently
269
+ loader = prepare_loader(
270
+ file_name=None,
271
+ file_content=None,
272
+ is_capture_image=loader_extra_config.get('extract_images') if loader_extra_config else False,
273
+ page_number=loader_extra_config.get('page_number') if loader_extra_config else None,
274
+ sheet_name=loader_extra_config.get('sheet_name') if loader_extra_config else None,
275
+ llm=llm or (loader_extra_config.get('llm') if loader_extra_config else None),
276
+ file_path=temp_file_path,
277
+ excel_by_sheets=loader_extra_config.get('excel_by_sheets') if loader_extra_config else False,
278
+ prompt=prompt or (loader_extra_config.get('prompt') if loader_extra_config else image_processing_prompt),
279
+ )
280
+
281
+ documents = loader.load()
282
+ page_contents = [doc.page_content for doc in documents]
283
+ return "\n".join(page_contents)
284
+ finally:
285
+ if temp_file_path and os.path.exists(temp_file_path):
286
+ os.remove(temp_file_path)
287
+
288
+
215
289
  def process_document_by_type(content, extension_source: str, document: Document = None, llm = None, chunking_config=None) \
216
290
  -> Generator[Document, None, None]:
217
291
  """Process the content of a file based on its type using a configured loader cosidering the origin document."""
@@ -338,4 +412,4 @@ def file_extension_by_chunker(chunker_name: str) -> str | None:
338
412
  return ".xml"
339
413
  if name == "csv":
340
414
  return ".csv"
341
- return None
415
+ return None