tool_manager.py 25 KB

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