tool_manager.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. import json
  2. import logging
  3. import mimetypes
  4. from collections.abc import Generator
  5. from os import listdir, path
  6. from threading import Lock
  7. from typing import Any, Optional, Union
  8. from configs import dify_config
  9. from core.agent.entities import AgentToolEntity
  10. from core.app.entities.app_invoke_entities import InvokeFrom
  11. from core.helper.module_import_helper import load_single_subclass_from_source
  12. from core.helper.position_helper import is_filtered
  13. from core.model_runtime.utils.encoders import jsonable_encoder
  14. from core.tools.entities.api_entities import UserToolProvider, UserToolProviderTypeLiteral
  15. from core.tools.entities.common_entities import I18nObject
  16. from core.tools.entities.tool_entities import ApiProviderAuthType, ToolInvokeFrom, ToolParameter
  17. from core.tools.errors import ToolProviderNotFoundError
  18. from core.tools.provider.api_tool_provider import ApiToolProviderController
  19. from core.tools.provider.builtin._positions import BuiltinToolProviderSort
  20. from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
  21. from core.tools.tool.api_tool import ApiTool
  22. from core.tools.tool.builtin_tool import BuiltinTool
  23. from core.tools.tool.tool import Tool
  24. from core.tools.tool_label_manager import ToolLabelManager
  25. from core.tools.utils.configuration import ToolConfigurationManager, ToolParameterConfigurationManager
  26. from extensions.ext_database import db
  27. from models.tools import ApiToolProvider, BuiltinToolProvider, WorkflowToolProvider
  28. from services.tools.tools_transform_service import ToolTransformService
  29. logger = logging.getLogger(__name__)
  30. class ToolManager:
  31. _builtin_provider_lock = Lock()
  32. _builtin_providers = {}
  33. _builtin_providers_loaded = False
  34. _builtin_tools_labels = {}
  35. @classmethod
  36. def get_builtin_provider(cls, provider: str) -> BuiltinToolProviderController:
  37. """
  38. get the builtin provider
  39. :param provider: the name of the provider
  40. :return: the provider
  41. """
  42. if len(cls._builtin_providers) == 0:
  43. # init the builtin providers
  44. cls.load_builtin_providers_cache()
  45. if provider not in cls._builtin_providers:
  46. raise ToolProviderNotFoundError(f"builtin provider {provider} not found")
  47. return cls._builtin_providers[provider]
  48. @classmethod
  49. def get_builtin_tool(cls, provider: str, tool_name: str) -> BuiltinTool:
  50. """
  51. get the builtin tool
  52. :param provider: the name of the provider
  53. :param tool_name: the name of the tool
  54. :return: the provider, the tool
  55. """
  56. provider_controller = cls.get_builtin_provider(provider)
  57. tool = provider_controller.get_tool(tool_name)
  58. return tool
  59. @classmethod
  60. def get_tool(
  61. cls, provider_type: str, provider_id: str, tool_name: str, tenant_id: Optional[str] = None
  62. ) -> Union[BuiltinTool, ApiTool]:
  63. """
  64. get the tool
  65. :param provider_type: the type of the provider
  66. :param provider_name: the name of the provider
  67. :param tool_name: the name of the tool
  68. :return: the tool
  69. """
  70. if provider_type == "builtin":
  71. return cls.get_builtin_tool(provider_id, tool_name)
  72. elif provider_type == "api":
  73. if tenant_id is None:
  74. raise ValueError("tenant id is required for api provider")
  75. api_provider, _ = cls.get_api_provider_controller(tenant_id, provider_id)
  76. return api_provider.get_tool(tool_name)
  77. elif provider_type == "app":
  78. raise NotImplementedError("app provider not implemented")
  79. else:
  80. raise ToolProviderNotFoundError(f"provider type {provider_type} not found")
  81. @classmethod
  82. def get_tool_runtime(
  83. cls,
  84. provider_type: str,
  85. provider_id: str,
  86. tool_name: str,
  87. tenant_id: str,
  88. invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
  89. tool_invoke_from: ToolInvokeFrom = ToolInvokeFrom.AGENT,
  90. ) -> Union[BuiltinTool, ApiTool]:
  91. """
  92. get the tool runtime
  93. :param provider_type: the type of the provider
  94. :param provider_name: the name of the provider
  95. :param tool_name: the name of the tool
  96. :return: the tool
  97. """
  98. if provider_type == "builtin":
  99. builtin_tool = cls.get_builtin_tool(provider_id, tool_name)
  100. # check if the builtin tool need credentials
  101. provider_controller = cls.get_builtin_provider(provider_id)
  102. if not provider_controller.need_credentials:
  103. return builtin_tool.fork_tool_runtime(
  104. runtime={
  105. "tenant_id": tenant_id,
  106. "credentials": {},
  107. "invoke_from": invoke_from,
  108. "tool_invoke_from": tool_invoke_from,
  109. }
  110. )
  111. # get credentials
  112. builtin_provider: BuiltinToolProvider = (
  113. db.session.query(BuiltinToolProvider)
  114. .filter(
  115. BuiltinToolProvider.tenant_id == tenant_id,
  116. BuiltinToolProvider.provider == provider_id,
  117. )
  118. .first()
  119. )
  120. if builtin_provider is None:
  121. raise ToolProviderNotFoundError(f"builtin provider {provider_id} not found")
  122. # decrypt the credentials
  123. credentials = builtin_provider.credentials
  124. controller = cls.get_builtin_provider(provider_id)
  125. tool_configuration = ToolConfigurationManager(tenant_id=tenant_id, provider_controller=controller)
  126. decrypted_credentials = tool_configuration.decrypt_tool_credentials(credentials)
  127. return builtin_tool.fork_tool_runtime(
  128. runtime={
  129. "tenant_id": tenant_id,
  130. "credentials": decrypted_credentials,
  131. "runtime_parameters": {},
  132. "invoke_from": invoke_from,
  133. "tool_invoke_from": tool_invoke_from,
  134. }
  135. )
  136. elif provider_type == "api":
  137. if tenant_id is None:
  138. raise ValueError("tenant id is required for api provider")
  139. api_provider, credentials = cls.get_api_provider_controller(tenant_id, provider_id)
  140. # decrypt the credentials
  141. tool_configuration = ToolConfigurationManager(tenant_id=tenant_id, provider_controller=api_provider)
  142. decrypted_credentials = tool_configuration.decrypt_tool_credentials(credentials)
  143. return api_provider.get_tool(tool_name).fork_tool_runtime(
  144. runtime={
  145. "tenant_id": tenant_id,
  146. "credentials": decrypted_credentials,
  147. "invoke_from": invoke_from,
  148. "tool_invoke_from": tool_invoke_from,
  149. }
  150. )
  151. elif provider_type == "workflow":
  152. workflow_provider = (
  153. db.session.query(WorkflowToolProvider)
  154. .filter(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == provider_id)
  155. .first()
  156. )
  157. if workflow_provider is None:
  158. raise ToolProviderNotFoundError(f"workflow provider {provider_id} not found")
  159. controller = ToolTransformService.workflow_provider_to_controller(db_provider=workflow_provider)
  160. return controller.get_tools(user_id=None, tenant_id=workflow_provider.tenant_id)[0].fork_tool_runtime(
  161. runtime={
  162. "tenant_id": tenant_id,
  163. "credentials": {},
  164. "invoke_from": invoke_from,
  165. "tool_invoke_from": tool_invoke_from,
  166. }
  167. )
  168. elif provider_type == "app":
  169. raise NotImplementedError("app provider not implemented")
  170. else:
  171. raise ToolProviderNotFoundError(f"provider type {provider_type} not found")
  172. @classmethod
  173. def _init_runtime_parameter(cls, parameter_rule: ToolParameter, parameters: dict):
  174. """
  175. init runtime parameter
  176. """
  177. parameter_value = parameters.get(parameter_rule.name)
  178. if not parameter_value and parameter_value != 0:
  179. # get default value
  180. parameter_value = parameter_rule.default
  181. if not parameter_value and parameter_rule.required:
  182. raise ValueError(f"tool parameter {parameter_rule.name} not found in tool config")
  183. if parameter_rule.type == ToolParameter.ToolParameterType.SELECT:
  184. # check if tool_parameter_config in options
  185. options = [x.value for x in parameter_rule.options]
  186. if parameter_value is not None and parameter_value not in options:
  187. raise ValueError(
  188. f"tool parameter {parameter_rule.name} value {parameter_value} not in options {options}"
  189. )
  190. return parameter_rule.type.cast_value(parameter_value)
  191. @classmethod
  192. def get_agent_tool_runtime(
  193. cls, tenant_id: str, app_id: str, agent_tool: AgentToolEntity, invoke_from: InvokeFrom = InvokeFrom.DEBUGGER
  194. ) -> Tool:
  195. """
  196. get the agent tool runtime
  197. """
  198. tool_entity = cls.get_tool_runtime(
  199. provider_type=agent_tool.provider_type,
  200. provider_id=agent_tool.provider_id,
  201. tool_name=agent_tool.tool_name,
  202. tenant_id=tenant_id,
  203. invoke_from=invoke_from,
  204. tool_invoke_from=ToolInvokeFrom.AGENT,
  205. )
  206. runtime_parameters = {}
  207. parameters = tool_entity.get_all_runtime_parameters()
  208. for parameter in parameters:
  209. # check file types
  210. if parameter.type in {
  211. ToolParameter.ToolParameterType.SYSTEM_FILES,
  212. ToolParameter.ToolParameterType.FILE,
  213. ToolParameter.ToolParameterType.FILES,
  214. }:
  215. raise ValueError(f"file type parameter {parameter.name} not supported in agent")
  216. if parameter.form == ToolParameter.ToolParameterForm.FORM:
  217. # save tool parameter to tool entity memory
  218. value = cls._init_runtime_parameter(parameter, agent_tool.tool_parameters)
  219. runtime_parameters[parameter.name] = value
  220. # decrypt runtime parameters
  221. encryption_manager = ToolParameterConfigurationManager(
  222. tenant_id=tenant_id,
  223. tool_runtime=tool_entity,
  224. provider_name=agent_tool.provider_id,
  225. provider_type=agent_tool.provider_type,
  226. identity_id=f"AGENT.{app_id}",
  227. )
  228. runtime_parameters = encryption_manager.decrypt_tool_parameters(runtime_parameters)
  229. tool_entity.runtime.runtime_parameters.update(runtime_parameters)
  230. return tool_entity
  231. @classmethod
  232. def get_workflow_tool_runtime(
  233. cls,
  234. tenant_id: str,
  235. app_id: str,
  236. node_id: str,
  237. workflow_tool: "ToolEntity",
  238. invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
  239. ) -> Tool:
  240. """
  241. get the workflow tool runtime
  242. """
  243. tool_entity = cls.get_tool_runtime(
  244. provider_type=workflow_tool.provider_type,
  245. provider_id=workflow_tool.provider_id,
  246. tool_name=workflow_tool.tool_name,
  247. tenant_id=tenant_id,
  248. invoke_from=invoke_from,
  249. tool_invoke_from=ToolInvokeFrom.WORKFLOW,
  250. )
  251. runtime_parameters = {}
  252. parameters = tool_entity.get_all_runtime_parameters()
  253. for parameter in parameters:
  254. # save tool parameter to tool entity memory
  255. if parameter.form == ToolParameter.ToolParameterForm.FORM:
  256. value = cls._init_runtime_parameter(parameter, workflow_tool.tool_configurations)
  257. runtime_parameters[parameter.name] = value
  258. # decrypt runtime parameters
  259. encryption_manager = ToolParameterConfigurationManager(
  260. tenant_id=tenant_id,
  261. tool_runtime=tool_entity,
  262. provider_name=workflow_tool.provider_id,
  263. provider_type=workflow_tool.provider_type,
  264. identity_id=f"WORKFLOW.{app_id}.{node_id}",
  265. )
  266. if runtime_parameters:
  267. runtime_parameters = encryption_manager.decrypt_tool_parameters(runtime_parameters)
  268. tool_entity.runtime.runtime_parameters.update(runtime_parameters)
  269. return tool_entity
  270. @classmethod
  271. def get_builtin_provider_icon(cls, provider: str) -> tuple[str, str]:
  272. """
  273. get the absolute path of the icon of the builtin provider
  274. :param provider: the name of the provider
  275. :return: the absolute path of the icon, the mime type of the icon
  276. """
  277. # get provider
  278. provider_controller = cls.get_builtin_provider(provider)
  279. absolute_path = path.join(
  280. path.dirname(path.realpath(__file__)),
  281. "provider",
  282. "builtin",
  283. provider,
  284. "_assets",
  285. provider_controller.identity.icon,
  286. )
  287. # check if the icon exists
  288. if not path.exists(absolute_path):
  289. raise ToolProviderNotFoundError(f"builtin provider {provider} icon not found")
  290. # get the mime type
  291. mime_type, _ = mimetypes.guess_type(absolute_path)
  292. mime_type = mime_type or "application/octet-stream"
  293. return absolute_path, mime_type
  294. @classmethod
  295. def list_builtin_providers(cls) -> Generator[BuiltinToolProviderController, None, None]:
  296. # use cache first
  297. if cls._builtin_providers_loaded:
  298. yield from list(cls._builtin_providers.values())
  299. return
  300. with cls._builtin_provider_lock:
  301. if cls._builtin_providers_loaded:
  302. yield from list(cls._builtin_providers.values())
  303. return
  304. yield from cls._list_builtin_providers()
  305. @classmethod
  306. def _list_builtin_providers(cls) -> Generator[BuiltinToolProviderController, None, None]:
  307. """
  308. list all the builtin providers
  309. """
  310. for provider in listdir(path.join(path.dirname(path.realpath(__file__)), "provider", "builtin")):
  311. if provider.startswith("__"):
  312. continue
  313. if path.isdir(path.join(path.dirname(path.realpath(__file__)), "provider", "builtin", provider)):
  314. if provider.startswith("__"):
  315. continue
  316. # init provider
  317. try:
  318. provider_class = load_single_subclass_from_source(
  319. module_name=f"core.tools.provider.builtin.{provider}.{provider}",
  320. script_path=path.join(
  321. path.dirname(path.realpath(__file__)), "provider", "builtin", provider, f"{provider}.py"
  322. ),
  323. parent_type=BuiltinToolProviderController,
  324. )
  325. provider: BuiltinToolProviderController = provider_class()
  326. cls._builtin_providers[provider.identity.name] = provider
  327. for tool in provider.get_tools():
  328. cls._builtin_tools_labels[tool.identity.name] = tool.identity.label
  329. yield provider
  330. except Exception as e:
  331. logger.error(f"load builtin provider {provider} error: {e}")
  332. continue
  333. # set builtin providers loaded
  334. cls._builtin_providers_loaded = True
  335. @classmethod
  336. def load_builtin_providers_cache(cls):
  337. for _ in cls.list_builtin_providers():
  338. pass
  339. @classmethod
  340. def clear_builtin_providers_cache(cls):
  341. cls._builtin_providers = {}
  342. cls._builtin_providers_loaded = False
  343. @classmethod
  344. def get_tool_label(cls, tool_name: str) -> Union[I18nObject, None]:
  345. """
  346. get the tool label
  347. :param tool_name: the name of the tool
  348. :return: the label of the tool
  349. """
  350. if len(cls._builtin_tools_labels) == 0:
  351. # init the builtin providers
  352. cls.load_builtin_providers_cache()
  353. if tool_name not in cls._builtin_tools_labels:
  354. return None
  355. return cls._builtin_tools_labels[tool_name]
  356. @classmethod
  357. def user_list_providers(
  358. cls, user_id: str, tenant_id: str, typ: UserToolProviderTypeLiteral
  359. ) -> list[UserToolProvider]:
  360. result_providers: dict[str, UserToolProvider] = {}
  361. filters = []
  362. if not typ:
  363. filters.extend(["builtin", "api", "workflow"])
  364. else:
  365. filters.append(typ)
  366. if "builtin" in filters:
  367. # get builtin providers
  368. builtin_providers = cls.list_builtin_providers()
  369. # get db builtin providers
  370. db_builtin_providers: list[BuiltinToolProvider] = (
  371. db.session.query(BuiltinToolProvider).filter(BuiltinToolProvider.tenant_id == tenant_id).all()
  372. )
  373. find_db_builtin_provider = lambda provider: next(
  374. (x for x in db_builtin_providers if x.provider == provider), None
  375. )
  376. # append builtin providers
  377. for provider in builtin_providers:
  378. # handle include, exclude
  379. if is_filtered(
  380. include_set=dify_config.POSITION_TOOL_INCLUDES_SET,
  381. exclude_set=dify_config.POSITION_TOOL_EXCLUDES_SET,
  382. data=provider,
  383. name_func=lambda x: x.identity.name,
  384. ):
  385. continue
  386. user_provider = ToolTransformService.builtin_provider_to_user_provider(
  387. provider_controller=provider,
  388. db_provider=find_db_builtin_provider(provider.identity.name),
  389. decrypt_credentials=False,
  390. )
  391. result_providers[provider.identity.name] = user_provider
  392. # get db api providers
  393. if "api" in filters:
  394. db_api_providers: list[ApiToolProvider] = (
  395. db.session.query(ApiToolProvider).filter(ApiToolProvider.tenant_id == tenant_id).all()
  396. )
  397. api_provider_controllers = [
  398. {"provider": provider, "controller": ToolTransformService.api_provider_to_controller(provider)}
  399. for provider in db_api_providers
  400. ]
  401. # get labels
  402. labels = ToolLabelManager.get_tools_labels([x["controller"] for x in api_provider_controllers])
  403. for api_provider_controller in api_provider_controllers:
  404. user_provider = ToolTransformService.api_provider_to_user_provider(
  405. provider_controller=api_provider_controller["controller"],
  406. db_provider=api_provider_controller["provider"],
  407. decrypt_credentials=False,
  408. labels=labels.get(api_provider_controller["controller"].provider_id, []),
  409. )
  410. result_providers[f"api_provider.{user_provider.name}"] = user_provider
  411. if "workflow" in filters:
  412. # get workflow providers
  413. workflow_providers: list[WorkflowToolProvider] = (
  414. db.session.query(WorkflowToolProvider).filter(WorkflowToolProvider.tenant_id == tenant_id).all()
  415. )
  416. workflow_provider_controllers = []
  417. for provider in workflow_providers:
  418. try:
  419. workflow_provider_controllers.append(
  420. ToolTransformService.workflow_provider_to_controller(db_provider=provider)
  421. )
  422. except Exception as e:
  423. # app has been deleted
  424. pass
  425. labels = ToolLabelManager.get_tools_labels(workflow_provider_controllers)
  426. for provider_controller in workflow_provider_controllers:
  427. user_provider = ToolTransformService.workflow_provider_to_user_provider(
  428. provider_controller=provider_controller,
  429. labels=labels.get(provider_controller.provider_id, []),
  430. )
  431. result_providers[f"workflow_provider.{user_provider.name}"] = user_provider
  432. return BuiltinToolProviderSort.sort(list(result_providers.values()))
  433. @classmethod
  434. def get_api_provider_controller(
  435. cls, tenant_id: str, provider_id: str
  436. ) -> tuple[ApiToolProviderController, dict[str, Any]]:
  437. """
  438. get the api provider
  439. :param provider_name: the name of the provider
  440. :return: the provider controller, the credentials
  441. """
  442. provider: ApiToolProvider = (
  443. db.session.query(ApiToolProvider)
  444. .filter(
  445. ApiToolProvider.id == provider_id,
  446. ApiToolProvider.tenant_id == tenant_id,
  447. )
  448. .first()
  449. )
  450. if provider is None:
  451. raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
  452. controller = ApiToolProviderController.from_db(
  453. provider,
  454. ApiProviderAuthType.API_KEY if provider.credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE,
  455. )
  456. controller.load_bundled_tools(provider.tools)
  457. return controller, provider.credentials
  458. @classmethod
  459. def user_get_api_provider(cls, provider: str, tenant_id: str) -> dict:
  460. """
  461. get api provider
  462. """
  463. """
  464. get tool provider
  465. """
  466. provider: ApiToolProvider = (
  467. db.session.query(ApiToolProvider)
  468. .filter(
  469. ApiToolProvider.tenant_id == tenant_id,
  470. ApiToolProvider.name == provider,
  471. )
  472. .first()
  473. )
  474. if provider is None:
  475. raise ValueError(f"you have not added provider {provider}")
  476. try:
  477. credentials = json.loads(provider.credentials_str) or {}
  478. except:
  479. credentials = {}
  480. # package tool provider controller
  481. controller = ApiToolProviderController.from_db(
  482. provider, ApiProviderAuthType.API_KEY if credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE
  483. )
  484. # init tool configuration
  485. tool_configuration = ToolConfigurationManager(tenant_id=tenant_id, provider_controller=controller)
  486. decrypted_credentials = tool_configuration.decrypt_tool_credentials(credentials)
  487. masked_credentials = tool_configuration.mask_tool_credentials(decrypted_credentials)
  488. try:
  489. icon = json.loads(provider.icon)
  490. except:
  491. icon = {"background": "#252525", "content": "\ud83d\ude01"}
  492. # add tool labels
  493. labels = ToolLabelManager.get_tool_labels(controller)
  494. return jsonable_encoder(
  495. {
  496. "schema_type": provider.schema_type,
  497. "schema": provider.schema,
  498. "tools": provider.tools,
  499. "icon": icon,
  500. "description": provider.description,
  501. "credentials": masked_credentials,
  502. "privacy_policy": provider.privacy_policy,
  503. "custom_disclaimer": provider.custom_disclaimer,
  504. "labels": labels,
  505. }
  506. )
  507. @classmethod
  508. def get_tool_icon(cls, tenant_id: str, provider_type: str, provider_id: str) -> Union[str, dict]:
  509. """
  510. get the tool icon
  511. :param tenant_id: the id of the tenant
  512. :param provider_type: the type of the provider
  513. :param provider_id: the id of the provider
  514. :return:
  515. """
  516. provider_type = provider_type
  517. provider_id = provider_id
  518. if provider_type == "builtin":
  519. return (
  520. dify_config.CONSOLE_API_URL
  521. + "/console/api/workspaces/current/tool-provider/builtin/"
  522. + provider_id
  523. + "/icon"
  524. )
  525. elif provider_type == "api":
  526. try:
  527. provider: ApiToolProvider = (
  528. db.session.query(ApiToolProvider)
  529. .filter(ApiToolProvider.tenant_id == tenant_id, ApiToolProvider.id == provider_id)
  530. .first()
  531. )
  532. return json.loads(provider.icon)
  533. except:
  534. return {"background": "#252525", "content": "\ud83d\ude01"}
  535. elif provider_type == "workflow":
  536. provider: WorkflowToolProvider = (
  537. db.session.query(WorkflowToolProvider)
  538. .filter(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == provider_id)
  539. .first()
  540. )
  541. if provider is None:
  542. raise ToolProviderNotFoundError(f"workflow provider {provider_id} not found")
  543. return json.loads(provider.icon)
  544. else:
  545. raise ValueError(f"provider type {provider_type} not found")
  546. ToolManager.load_builtin_providers_cache()