You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tool_manager.py 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. import json
  2. import logging
  3. import mimetypes
  4. import time
  5. from collections.abc import Generator, Mapping
  6. from os import listdir, path
  7. from threading import Lock
  8. from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast
  9. import sqlalchemy as sa
  10. from pydantic import TypeAdapter
  11. from sqlalchemy.orm import Session
  12. from yarl import URL
  13. import contexts
  14. from core.helper.provider_cache import ToolProviderCredentialsCache
  15. from core.plugin.entities.plugin import ToolProviderID
  16. from core.plugin.impl.oauth import OAuthHandler
  17. from core.plugin.impl.tool import PluginToolManager
  18. from core.tools.__base.tool_provider import ToolProviderController
  19. from core.tools.__base.tool_runtime import ToolRuntime
  20. from core.tools.mcp_tool.provider import MCPToolProviderController
  21. from core.tools.mcp_tool.tool import MCPTool
  22. from core.tools.plugin_tool.provider import PluginToolProviderController
  23. from core.tools.plugin_tool.tool import PluginTool
  24. from core.tools.utils.uuid_utils import is_valid_uuid
  25. from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
  26. from core.workflow.entities.variable_pool import VariablePool
  27. from services.tools.mcp_tools_manage_service import MCPToolManageService
  28. if TYPE_CHECKING:
  29. from core.workflow.nodes.tool.entities import ToolEntity
  30. from configs import dify_config
  31. from core.agent.entities import AgentToolEntity
  32. from core.app.entities.app_invoke_entities import InvokeFrom
  33. from core.helper.module_import_helper import load_single_subclass_from_source
  34. from core.helper.position_helper import is_filtered
  35. from core.model_runtime.utils.encoders import jsonable_encoder
  36. from core.tools.__base.tool import Tool
  37. from core.tools.builtin_tool.provider import BuiltinToolProviderController
  38. from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
  39. from core.tools.builtin_tool.tool import BuiltinTool
  40. from core.tools.custom_tool.provider import ApiToolProviderController
  41. from core.tools.custom_tool.tool import ApiTool
  42. from core.tools.entities.api_entities import ToolProviderApiEntity, ToolProviderTypeApiLiteral
  43. from core.tools.entities.common_entities import I18nObject
  44. from core.tools.entities.tool_entities import (
  45. ApiProviderAuthType,
  46. CredentialType,
  47. ToolInvokeFrom,
  48. ToolParameter,
  49. ToolProviderType,
  50. )
  51. from core.tools.errors import ToolProviderNotFoundError
  52. from core.tools.tool_label_manager import ToolLabelManager
  53. from core.tools.utils.configuration import (
  54. ToolParameterConfigurationManager,
  55. )
  56. from core.tools.utils.encryption import create_provider_encrypter, create_tool_provider_encrypter
  57. from core.tools.workflow_as_tool.tool import WorkflowTool
  58. from extensions.ext_database import db
  59. from models.tools import ApiToolProvider, BuiltinToolProvider, MCPToolProvider, WorkflowToolProvider
  60. from services.tools.tools_transform_service import ToolTransformService
  61. logger = logging.getLogger(__name__)
  62. class ToolManager:
  63. _builtin_provider_lock = Lock()
  64. _hardcoded_providers: dict[str, BuiltinToolProviderController] = {}
  65. _builtin_providers_loaded = False
  66. _builtin_tools_labels: dict[str, Union[I18nObject, None]] = {}
  67. @classmethod
  68. def get_hardcoded_provider(cls, provider: str) -> BuiltinToolProviderController:
  69. """
  70. get the hardcoded provider
  71. """
  72. if len(cls._hardcoded_providers) == 0:
  73. # init the builtin providers
  74. cls.load_hardcoded_providers_cache()
  75. return cls._hardcoded_providers[provider]
  76. @classmethod
  77. def get_builtin_provider(
  78. cls, provider: str, tenant_id: str
  79. ) -> BuiltinToolProviderController | PluginToolProviderController:
  80. """
  81. get the builtin provider
  82. :param provider: the name of the provider
  83. :param tenant_id: the id of the tenant
  84. :return: the provider
  85. """
  86. # split provider to
  87. if len(cls._hardcoded_providers) == 0:
  88. # init the builtin providers
  89. cls.load_hardcoded_providers_cache()
  90. if provider not in cls._hardcoded_providers:
  91. # get plugin provider
  92. plugin_provider = cls.get_plugin_provider(provider, tenant_id)
  93. if plugin_provider:
  94. return plugin_provider
  95. return cls._hardcoded_providers[provider]
  96. @classmethod
  97. def get_plugin_provider(cls, provider: str, tenant_id: str) -> PluginToolProviderController:
  98. """
  99. get the plugin provider
  100. """
  101. # check if context is set
  102. try:
  103. contexts.plugin_tool_providers.get()
  104. except LookupError:
  105. contexts.plugin_tool_providers.set({})
  106. contexts.plugin_tool_providers_lock.set(Lock())
  107. plugin_tool_providers = contexts.plugin_tool_providers.get()
  108. if provider in plugin_tool_providers:
  109. return plugin_tool_providers[provider]
  110. with contexts.plugin_tool_providers_lock.get():
  111. # double check
  112. plugin_tool_providers = contexts.plugin_tool_providers.get()
  113. if provider in plugin_tool_providers:
  114. return plugin_tool_providers[provider]
  115. manager = PluginToolManager()
  116. provider_entity = manager.fetch_tool_provider(tenant_id, provider)
  117. if not provider_entity:
  118. raise ToolProviderNotFoundError(f"plugin provider {provider} not found")
  119. controller = PluginToolProviderController(
  120. entity=provider_entity.declaration,
  121. plugin_id=provider_entity.plugin_id,
  122. plugin_unique_identifier=provider_entity.plugin_unique_identifier,
  123. tenant_id=tenant_id,
  124. )
  125. plugin_tool_providers[provider] = controller
  126. return controller
  127. @classmethod
  128. def get_tool_runtime(
  129. cls,
  130. provider_type: ToolProviderType,
  131. provider_id: str,
  132. tool_name: str,
  133. tenant_id: str,
  134. invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
  135. tool_invoke_from: ToolInvokeFrom = ToolInvokeFrom.AGENT,
  136. credential_id: Optional[str] = None,
  137. ) -> Union[BuiltinTool, PluginTool, ApiTool, WorkflowTool, MCPTool]:
  138. """
  139. get the tool runtime
  140. :param provider_type: the type of the provider
  141. :param provider_id: the id of the provider
  142. :param tool_name: the name of the tool
  143. :param tenant_id: the tenant id
  144. :param invoke_from: invoke from
  145. :param tool_invoke_from: the tool invoke from
  146. :param credential_id: the credential id
  147. :return: the tool
  148. """
  149. if provider_type == ToolProviderType.BUILT_IN:
  150. # check if the builtin tool need credentials
  151. provider_controller = cls.get_builtin_provider(provider_id, tenant_id)
  152. builtin_tool = provider_controller.get_tool(tool_name)
  153. if not builtin_tool:
  154. raise ToolProviderNotFoundError(f"builtin tool {tool_name} not found")
  155. if not provider_controller.need_credentials:
  156. return cast(
  157. BuiltinTool,
  158. builtin_tool.fork_tool_runtime(
  159. runtime=ToolRuntime(
  160. tenant_id=tenant_id,
  161. credentials={},
  162. invoke_from=invoke_from,
  163. tool_invoke_from=tool_invoke_from,
  164. )
  165. ),
  166. )
  167. builtin_provider = None
  168. if isinstance(provider_controller, PluginToolProviderController):
  169. provider_id_entity = ToolProviderID(provider_id)
  170. # get specific credentials
  171. if is_valid_uuid(credential_id):
  172. try:
  173. builtin_provider = (
  174. db.session.query(BuiltinToolProvider)
  175. .where(
  176. BuiltinToolProvider.tenant_id == tenant_id,
  177. BuiltinToolProvider.id == credential_id,
  178. )
  179. .first()
  180. )
  181. except Exception as e:
  182. builtin_provider = None
  183. logger.info("Error getting builtin provider %s:%s", credential_id, e, exc_info=True)
  184. # if the provider has been deleted, raise an error
  185. if builtin_provider is None:
  186. raise ToolProviderNotFoundError(f"provider has been deleted: {credential_id}")
  187. # fallback to the default provider
  188. if builtin_provider is None:
  189. # use the default provider
  190. builtin_provider = (
  191. db.session.query(BuiltinToolProvider)
  192. .where(
  193. BuiltinToolProvider.tenant_id == tenant_id,
  194. (BuiltinToolProvider.provider == str(provider_id_entity))
  195. | (BuiltinToolProvider.provider == provider_id_entity.provider_name),
  196. )
  197. .order_by(BuiltinToolProvider.is_default.desc(), BuiltinToolProvider.created_at.asc())
  198. .first()
  199. )
  200. if builtin_provider is None:
  201. raise ToolProviderNotFoundError(f"no default provider for {provider_id}")
  202. else:
  203. builtin_provider = (
  204. db.session.query(BuiltinToolProvider)
  205. .where(BuiltinToolProvider.tenant_id == tenant_id, (BuiltinToolProvider.provider == provider_id))
  206. .order_by(BuiltinToolProvider.is_default.desc(), BuiltinToolProvider.created_at.asc())
  207. .first()
  208. )
  209. if builtin_provider is None:
  210. raise ToolProviderNotFoundError(f"builtin provider {provider_id} not found")
  211. encrypter, cache = create_provider_encrypter(
  212. tenant_id=tenant_id,
  213. config=[
  214. x.to_basic_provider_config()
  215. for x in provider_controller.get_credentials_schema_by_type(builtin_provider.credential_type)
  216. ],
  217. cache=ToolProviderCredentialsCache(
  218. tenant_id=tenant_id, provider=provider_id, credential_id=builtin_provider.id
  219. ),
  220. )
  221. # decrypt the credentials
  222. decrypted_credentials: Mapping[str, Any] = encrypter.decrypt(builtin_provider.credentials)
  223. # check if the credentials is expired
  224. if builtin_provider.expires_at != -1 and (builtin_provider.expires_at - 60) < int(time.time()):
  225. # TODO: circular import
  226. from services.tools.builtin_tools_manage_service import BuiltinToolManageService
  227. # refresh the credentials
  228. tool_provider = ToolProviderID(provider_id)
  229. provider_name = tool_provider.provider_name
  230. redirect_uri = f"{dify_config.CONSOLE_API_URL}/console/api/oauth/plugin/{provider_id}/tool/callback"
  231. system_credentials = BuiltinToolManageService.get_oauth_client(tenant_id, provider_id)
  232. oauth_handler = OAuthHandler()
  233. # refresh the credentials
  234. refreshed_credentials = oauth_handler.refresh_credentials(
  235. tenant_id=tenant_id,
  236. user_id=builtin_provider.user_id,
  237. plugin_id=tool_provider.plugin_id,
  238. provider=provider_name,
  239. redirect_uri=redirect_uri,
  240. system_credentials=system_credentials or {},
  241. credentials=decrypted_credentials,
  242. )
  243. # update the credentials
  244. builtin_provider.encrypted_credentials = (
  245. TypeAdapter(dict[str, Any])
  246. .dump_json(encrypter.encrypt(dict(refreshed_credentials.credentials)))
  247. .decode("utf-8")
  248. )
  249. builtin_provider.expires_at = refreshed_credentials.expires_at
  250. db.session.commit()
  251. decrypted_credentials = refreshed_credentials.credentials
  252. cache.delete()
  253. return cast(
  254. BuiltinTool,
  255. builtin_tool.fork_tool_runtime(
  256. runtime=ToolRuntime(
  257. tenant_id=tenant_id,
  258. credentials=dict(decrypted_credentials),
  259. credential_type=CredentialType.of(builtin_provider.credential_type),
  260. runtime_parameters={},
  261. invoke_from=invoke_from,
  262. tool_invoke_from=tool_invoke_from,
  263. )
  264. ),
  265. )
  266. elif provider_type == ToolProviderType.API:
  267. api_provider, credentials = cls.get_api_provider_controller(tenant_id, provider_id)
  268. encrypter, _ = create_tool_provider_encrypter(
  269. tenant_id=tenant_id,
  270. controller=api_provider,
  271. )
  272. return cast(
  273. ApiTool,
  274. api_provider.get_tool(tool_name).fork_tool_runtime(
  275. runtime=ToolRuntime(
  276. tenant_id=tenant_id,
  277. credentials=encrypter.decrypt(credentials),
  278. invoke_from=invoke_from,
  279. tool_invoke_from=tool_invoke_from,
  280. )
  281. ),
  282. )
  283. elif provider_type == ToolProviderType.WORKFLOW:
  284. workflow_provider = (
  285. db.session.query(WorkflowToolProvider)
  286. .where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == provider_id)
  287. .first()
  288. )
  289. if workflow_provider is None:
  290. raise ToolProviderNotFoundError(f"workflow provider {provider_id} not found")
  291. controller = ToolTransformService.workflow_provider_to_controller(db_provider=workflow_provider)
  292. controller_tools: list[WorkflowTool] = controller.get_tools(tenant_id=workflow_provider.tenant_id)
  293. if controller_tools is None or len(controller_tools) == 0:
  294. raise ToolProviderNotFoundError(f"workflow provider {provider_id} not found")
  295. return cast(
  296. WorkflowTool,
  297. controller.get_tools(tenant_id=workflow_provider.tenant_id)[0].fork_tool_runtime(
  298. runtime=ToolRuntime(
  299. tenant_id=tenant_id,
  300. credentials={},
  301. invoke_from=invoke_from,
  302. tool_invoke_from=tool_invoke_from,
  303. )
  304. ),
  305. )
  306. elif provider_type == ToolProviderType.APP:
  307. raise NotImplementedError("app provider not implemented")
  308. elif provider_type == ToolProviderType.PLUGIN:
  309. return cls.get_plugin_provider(provider_id, tenant_id).get_tool(tool_name)
  310. elif provider_type == ToolProviderType.MCP:
  311. return cls.get_mcp_provider_controller(tenant_id, provider_id).get_tool(tool_name)
  312. else:
  313. raise ToolProviderNotFoundError(f"provider type {provider_type.value} not found")
  314. @classmethod
  315. def get_agent_tool_runtime(
  316. cls,
  317. tenant_id: str,
  318. app_id: str,
  319. agent_tool: AgentToolEntity,
  320. invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
  321. variable_pool: Optional[VariablePool] = None,
  322. ) -> Tool:
  323. """
  324. get the agent tool runtime
  325. """
  326. tool_entity = cls.get_tool_runtime(
  327. provider_type=agent_tool.provider_type,
  328. provider_id=agent_tool.provider_id,
  329. tool_name=agent_tool.tool_name,
  330. tenant_id=tenant_id,
  331. invoke_from=invoke_from,
  332. tool_invoke_from=ToolInvokeFrom.AGENT,
  333. credential_id=agent_tool.credential_id,
  334. )
  335. runtime_parameters = {}
  336. parameters = tool_entity.get_merged_runtime_parameters()
  337. runtime_parameters = cls._convert_tool_parameters_type(
  338. parameters, variable_pool, agent_tool.tool_parameters, typ="agent"
  339. )
  340. # decrypt runtime parameters
  341. encryption_manager = ToolParameterConfigurationManager(
  342. tenant_id=tenant_id,
  343. tool_runtime=tool_entity,
  344. provider_name=agent_tool.provider_id,
  345. provider_type=agent_tool.provider_type,
  346. identity_id=f"AGENT.{app_id}",
  347. )
  348. runtime_parameters = encryption_manager.decrypt_tool_parameters(runtime_parameters)
  349. if tool_entity.runtime is None or tool_entity.runtime.runtime_parameters is None:
  350. raise ValueError("runtime not found or runtime parameters not found")
  351. tool_entity.runtime.runtime_parameters.update(runtime_parameters)
  352. return tool_entity
  353. @classmethod
  354. def get_workflow_tool_runtime(
  355. cls,
  356. tenant_id: str,
  357. app_id: str,
  358. node_id: str,
  359. workflow_tool: "ToolEntity",
  360. invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
  361. variable_pool: Optional[VariablePool] = None,
  362. ) -> Tool:
  363. """
  364. get the workflow tool runtime
  365. """
  366. tool_runtime = cls.get_tool_runtime(
  367. provider_type=workflow_tool.provider_type,
  368. provider_id=workflow_tool.provider_id,
  369. tool_name=workflow_tool.tool_name,
  370. tenant_id=tenant_id,
  371. invoke_from=invoke_from,
  372. tool_invoke_from=ToolInvokeFrom.WORKFLOW,
  373. credential_id=workflow_tool.credential_id,
  374. )
  375. parameters = tool_runtime.get_merged_runtime_parameters()
  376. runtime_parameters = cls._convert_tool_parameters_type(
  377. parameters, variable_pool, workflow_tool.tool_configurations, typ="workflow"
  378. )
  379. # decrypt runtime parameters
  380. encryption_manager = ToolParameterConfigurationManager(
  381. tenant_id=tenant_id,
  382. tool_runtime=tool_runtime,
  383. provider_name=workflow_tool.provider_id,
  384. provider_type=workflow_tool.provider_type,
  385. identity_id=f"WORKFLOW.{app_id}.{node_id}",
  386. )
  387. if runtime_parameters:
  388. runtime_parameters = encryption_manager.decrypt_tool_parameters(runtime_parameters)
  389. tool_runtime.runtime.runtime_parameters.update(runtime_parameters)
  390. return tool_runtime
  391. @classmethod
  392. def get_tool_runtime_from_plugin(
  393. cls,
  394. tool_type: ToolProviderType,
  395. tenant_id: str,
  396. provider: str,
  397. tool_name: str,
  398. tool_parameters: dict[str, Any],
  399. credential_id: Optional[str] = None,
  400. ) -> Tool:
  401. """
  402. get tool runtime from plugin
  403. """
  404. tool_entity = cls.get_tool_runtime(
  405. provider_type=tool_type,
  406. provider_id=provider,
  407. tool_name=tool_name,
  408. tenant_id=tenant_id,
  409. invoke_from=InvokeFrom.SERVICE_API,
  410. tool_invoke_from=ToolInvokeFrom.PLUGIN,
  411. credential_id=credential_id,
  412. )
  413. runtime_parameters = {}
  414. parameters = tool_entity.get_merged_runtime_parameters()
  415. for parameter in parameters:
  416. if parameter.form == ToolParameter.ToolParameterForm.FORM:
  417. # save tool parameter to tool entity memory
  418. value = parameter.init_frontend_parameter(tool_parameters.get(parameter.name))
  419. runtime_parameters[parameter.name] = value
  420. tool_entity.runtime.runtime_parameters.update(runtime_parameters)
  421. return tool_entity
  422. @classmethod
  423. def get_hardcoded_provider_icon(cls, provider: str) -> tuple[str, str]:
  424. """
  425. get the absolute path of the icon of the hardcoded provider
  426. :param provider: the name of the provider
  427. :return: the absolute path of the icon, the mime type of the icon
  428. """
  429. # get provider
  430. provider_controller = cls.get_hardcoded_provider(provider)
  431. absolute_path = path.join(
  432. path.dirname(path.realpath(__file__)),
  433. "builtin_tool",
  434. "providers",
  435. provider,
  436. "_assets",
  437. provider_controller.entity.identity.icon,
  438. )
  439. # check if the icon exists
  440. if not path.exists(absolute_path):
  441. raise ToolProviderNotFoundError(f"builtin provider {provider} icon not found")
  442. # get the mime type
  443. mime_type, _ = mimetypes.guess_type(absolute_path)
  444. mime_type = mime_type or "application/octet-stream"
  445. return absolute_path, mime_type
  446. @classmethod
  447. def list_hardcoded_providers(cls):
  448. # use cache first
  449. if cls._builtin_providers_loaded:
  450. yield from list(cls._hardcoded_providers.values())
  451. return
  452. with cls._builtin_provider_lock:
  453. if cls._builtin_providers_loaded:
  454. yield from list(cls._hardcoded_providers.values())
  455. return
  456. yield from cls._list_hardcoded_providers()
  457. @classmethod
  458. def list_plugin_providers(cls, tenant_id: str) -> list[PluginToolProviderController]:
  459. """
  460. list all the plugin providers
  461. """
  462. manager = PluginToolManager()
  463. provider_entities = manager.fetch_tool_providers(tenant_id)
  464. return [
  465. PluginToolProviderController(
  466. entity=provider.declaration,
  467. plugin_id=provider.plugin_id,
  468. plugin_unique_identifier=provider.plugin_unique_identifier,
  469. tenant_id=tenant_id,
  470. )
  471. for provider in provider_entities
  472. ]
  473. @classmethod
  474. def list_builtin_providers(
  475. cls, tenant_id: str
  476. ) -> Generator[BuiltinToolProviderController | PluginToolProviderController, None, None]:
  477. """
  478. list all the builtin providers
  479. """
  480. yield from cls.list_hardcoded_providers()
  481. # get plugin providers
  482. yield from cls.list_plugin_providers(tenant_id)
  483. @classmethod
  484. def _list_hardcoded_providers(cls) -> Generator[BuiltinToolProviderController, None, None]:
  485. """
  486. list all the builtin providers
  487. """
  488. for provider_path in listdir(path.join(path.dirname(path.realpath(__file__)), "builtin_tool", "providers")):
  489. if provider_path.startswith("__"):
  490. continue
  491. if path.isdir(path.join(path.dirname(path.realpath(__file__)), "builtin_tool", "providers", provider_path)):
  492. if provider_path.startswith("__"):
  493. continue
  494. # init provider
  495. try:
  496. provider_class = load_single_subclass_from_source(
  497. module_name=f"core.tools.builtin_tool.providers.{provider_path}.{provider_path}",
  498. script_path=path.join(
  499. path.dirname(path.realpath(__file__)),
  500. "builtin_tool",
  501. "providers",
  502. provider_path,
  503. f"{provider_path}.py",
  504. ),
  505. parent_type=BuiltinToolProviderController,
  506. )
  507. provider: BuiltinToolProviderController = provider_class()
  508. cls._hardcoded_providers[provider.entity.identity.name] = provider
  509. for tool in provider.get_tools():
  510. cls._builtin_tools_labels[tool.entity.identity.name] = tool.entity.identity.label
  511. yield provider
  512. except Exception:
  513. logger.exception("load builtin provider %s", provider_path)
  514. continue
  515. # set builtin providers loaded
  516. cls._builtin_providers_loaded = True
  517. @classmethod
  518. def load_hardcoded_providers_cache(cls):
  519. for _ in cls.list_hardcoded_providers():
  520. pass
  521. @classmethod
  522. def clear_hardcoded_providers_cache(cls):
  523. cls._hardcoded_providers = {}
  524. cls._builtin_providers_loaded = False
  525. @classmethod
  526. def get_tool_label(cls, tool_name: str) -> Union[I18nObject, None]:
  527. """
  528. get the tool label
  529. :param tool_name: the name of the tool
  530. :return: the label of the tool
  531. """
  532. if len(cls._builtin_tools_labels) == 0:
  533. # init the builtin providers
  534. cls.load_hardcoded_providers_cache()
  535. if tool_name not in cls._builtin_tools_labels:
  536. return None
  537. return cls._builtin_tools_labels[tool_name]
  538. @classmethod
  539. def list_default_builtin_providers(cls, tenant_id: str) -> list[BuiltinToolProvider]:
  540. """
  541. list all the builtin providers
  542. """
  543. # according to multi credentials, select the one with is_default=True first, then created_at oldest
  544. # for compatibility with old version
  545. sql = """
  546. SELECT DISTINCT ON (tenant_id, provider) id
  547. FROM tool_builtin_providers
  548. WHERE tenant_id = :tenant_id
  549. ORDER BY tenant_id, provider, is_default DESC, created_at DESC
  550. """
  551. with Session(db.engine, autoflush=False) as session:
  552. ids = [row.id for row in session.execute(sa.text(sql), {"tenant_id": tenant_id}).all()]
  553. return session.query(BuiltinToolProvider).where(BuiltinToolProvider.id.in_(ids)).all()
  554. @classmethod
  555. def list_providers_from_api(
  556. cls, user_id: str, tenant_id: str, typ: ToolProviderTypeApiLiteral
  557. ) -> list[ToolProviderApiEntity]:
  558. result_providers: dict[str, ToolProviderApiEntity] = {}
  559. filters = []
  560. if not typ:
  561. filters.extend(["builtin", "api", "workflow", "mcp"])
  562. else:
  563. filters.append(typ)
  564. with db.session.no_autoflush:
  565. if "builtin" in filters:
  566. builtin_providers = cls.list_builtin_providers(tenant_id)
  567. # key: provider name, value: provider
  568. db_builtin_providers = {
  569. str(ToolProviderID(provider.provider)): provider
  570. for provider in cls.list_default_builtin_providers(tenant_id)
  571. }
  572. # append builtin providers
  573. for provider in builtin_providers:
  574. # handle include, exclude
  575. if is_filtered(
  576. include_set=cast(set[str], dify_config.POSITION_TOOL_INCLUDES_SET),
  577. exclude_set=cast(set[str], dify_config.POSITION_TOOL_EXCLUDES_SET),
  578. data=provider,
  579. name_func=lambda x: x.identity.name,
  580. ):
  581. continue
  582. user_provider = ToolTransformService.builtin_provider_to_user_provider(
  583. provider_controller=provider,
  584. db_provider=db_builtin_providers.get(provider.entity.identity.name),
  585. decrypt_credentials=False,
  586. )
  587. if isinstance(provider, PluginToolProviderController):
  588. result_providers[f"plugin_provider.{user_provider.name}"] = user_provider
  589. else:
  590. result_providers[f"builtin_provider.{user_provider.name}"] = user_provider
  591. # get db api providers
  592. if "api" in filters:
  593. db_api_providers: list[ApiToolProvider] = (
  594. db.session.query(ApiToolProvider).where(ApiToolProvider.tenant_id == tenant_id).all()
  595. )
  596. api_provider_controllers: list[dict[str, Any]] = [
  597. {"provider": provider, "controller": ToolTransformService.api_provider_to_controller(provider)}
  598. for provider in db_api_providers
  599. ]
  600. # get labels
  601. labels = ToolLabelManager.get_tools_labels([x["controller"] for x in api_provider_controllers])
  602. for api_provider_controller in api_provider_controllers:
  603. user_provider = ToolTransformService.api_provider_to_user_provider(
  604. provider_controller=api_provider_controller["controller"],
  605. db_provider=api_provider_controller["provider"],
  606. decrypt_credentials=False,
  607. labels=labels.get(api_provider_controller["controller"].provider_id, []),
  608. )
  609. result_providers[f"api_provider.{user_provider.name}"] = user_provider
  610. if "workflow" in filters:
  611. # get workflow providers
  612. workflow_providers: list[WorkflowToolProvider] = (
  613. db.session.query(WorkflowToolProvider).where(WorkflowToolProvider.tenant_id == tenant_id).all()
  614. )
  615. workflow_provider_controllers: list[WorkflowToolProviderController] = []
  616. for workflow_provider in workflow_providers:
  617. try:
  618. workflow_provider_controllers.append(
  619. ToolTransformService.workflow_provider_to_controller(db_provider=workflow_provider)
  620. )
  621. except Exception:
  622. # app has been deleted
  623. pass
  624. labels = ToolLabelManager.get_tools_labels(
  625. [cast(ToolProviderController, controller) for controller in workflow_provider_controllers]
  626. )
  627. for provider_controller in workflow_provider_controllers:
  628. user_provider = ToolTransformService.workflow_provider_to_user_provider(
  629. provider_controller=provider_controller,
  630. labels=labels.get(provider_controller.provider_id, []),
  631. )
  632. result_providers[f"workflow_provider.{user_provider.name}"] = user_provider
  633. if "mcp" in filters:
  634. mcp_providers = MCPToolManageService.retrieve_mcp_tools(tenant_id, for_list=True)
  635. for mcp_provider in mcp_providers:
  636. result_providers[f"mcp_provider.{mcp_provider.name}"] = mcp_provider
  637. return BuiltinToolProviderSort.sort(list(result_providers.values()))
  638. @classmethod
  639. def get_api_provider_controller(
  640. cls, tenant_id: str, provider_id: str
  641. ) -> tuple[ApiToolProviderController, dict[str, Any]]:
  642. """
  643. get the api provider
  644. :param tenant_id: the id of the tenant
  645. :param provider_id: the id of the provider
  646. :return: the provider controller, the credentials
  647. """
  648. provider: ApiToolProvider | None = (
  649. db.session.query(ApiToolProvider)
  650. .where(
  651. ApiToolProvider.id == provider_id,
  652. ApiToolProvider.tenant_id == tenant_id,
  653. )
  654. .first()
  655. )
  656. if provider is None:
  657. raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
  658. auth_type = ApiProviderAuthType.NONE
  659. provider_auth_type = provider.credentials.get("auth_type")
  660. if provider_auth_type in ("api_key_header", "api_key"): # backward compatibility
  661. auth_type = ApiProviderAuthType.API_KEY_HEADER
  662. elif provider_auth_type == "api_key_query":
  663. auth_type = ApiProviderAuthType.API_KEY_QUERY
  664. controller = ApiToolProviderController.from_db(
  665. provider,
  666. auth_type,
  667. )
  668. controller.load_bundled_tools(provider.tools)
  669. return controller, provider.credentials
  670. @classmethod
  671. def get_mcp_provider_controller(cls, tenant_id: str, provider_id: str) -> MCPToolProviderController:
  672. """
  673. get the api provider
  674. :param tenant_id: the id of the tenant
  675. :param provider_id: the id of the provider
  676. :return: the provider controller, the credentials
  677. """
  678. provider: MCPToolProvider | None = (
  679. db.session.query(MCPToolProvider)
  680. .where(
  681. MCPToolProvider.server_identifier == provider_id,
  682. MCPToolProvider.tenant_id == tenant_id,
  683. )
  684. .first()
  685. )
  686. if provider is None:
  687. raise ToolProviderNotFoundError(f"mcp provider {provider_id} not found")
  688. controller = MCPToolProviderController._from_db(provider)
  689. return controller
  690. @classmethod
  691. def user_get_api_provider(cls, provider: str, tenant_id: str) -> dict:
  692. """
  693. get api provider
  694. """
  695. provider_name = provider
  696. provider_obj: ApiToolProvider | None = (
  697. db.session.query(ApiToolProvider)
  698. .where(
  699. ApiToolProvider.tenant_id == tenant_id,
  700. ApiToolProvider.name == provider,
  701. )
  702. .first()
  703. )
  704. if provider_obj is None:
  705. raise ValueError(f"you have not added provider {provider_name}")
  706. try:
  707. credentials = json.loads(provider_obj.credentials_str) or {}
  708. except Exception:
  709. credentials = {}
  710. # package tool provider controller
  711. auth_type = ApiProviderAuthType.NONE
  712. credentials_auth_type = credentials.get("auth_type")
  713. if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility
  714. auth_type = ApiProviderAuthType.API_KEY_HEADER
  715. elif credentials_auth_type == "api_key_query":
  716. auth_type = ApiProviderAuthType.API_KEY_QUERY
  717. controller = ApiToolProviderController.from_db(
  718. provider_obj,
  719. auth_type,
  720. )
  721. # init tool configuration
  722. encrypter, _ = create_tool_provider_encrypter(
  723. tenant_id=tenant_id,
  724. controller=controller,
  725. )
  726. masked_credentials = encrypter.mask_tool_credentials(encrypter.decrypt(credentials))
  727. try:
  728. icon = json.loads(provider_obj.icon)
  729. except Exception:
  730. icon = {"background": "#252525", "content": "\ud83d\ude01"}
  731. # add tool labels
  732. labels = ToolLabelManager.get_tool_labels(controller)
  733. return cast(
  734. dict,
  735. jsonable_encoder(
  736. {
  737. "schema_type": provider_obj.schema_type,
  738. "schema": provider_obj.schema,
  739. "tools": provider_obj.tools,
  740. "icon": icon,
  741. "description": provider_obj.description,
  742. "credentials": masked_credentials,
  743. "privacy_policy": provider_obj.privacy_policy,
  744. "custom_disclaimer": provider_obj.custom_disclaimer,
  745. "labels": labels,
  746. }
  747. ),
  748. )
  749. @classmethod
  750. def generate_builtin_tool_icon_url(cls, provider_id: str) -> str:
  751. return str(
  752. URL(dify_config.CONSOLE_API_URL or "/")
  753. / "console"
  754. / "api"
  755. / "workspaces"
  756. / "current"
  757. / "tool-provider"
  758. / "builtin"
  759. / provider_id
  760. / "icon"
  761. )
  762. @classmethod
  763. def generate_plugin_tool_icon_url(cls, tenant_id: str, filename: str) -> str:
  764. return str(
  765. URL(dify_config.CONSOLE_API_URL or "/")
  766. / "console"
  767. / "api"
  768. / "workspaces"
  769. / "current"
  770. / "plugin"
  771. / "icon"
  772. % {"tenant_id": tenant_id, "filename": filename}
  773. )
  774. @classmethod
  775. def generate_workflow_tool_icon_url(cls, tenant_id: str, provider_id: str) -> dict:
  776. try:
  777. workflow_provider: WorkflowToolProvider | None = (
  778. db.session.query(WorkflowToolProvider)
  779. .where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == provider_id)
  780. .first()
  781. )
  782. if workflow_provider is None:
  783. raise ToolProviderNotFoundError(f"workflow provider {provider_id} not found")
  784. icon: dict = json.loads(workflow_provider.icon)
  785. return icon
  786. except Exception:
  787. return {"background": "#252525", "content": "\ud83d\ude01"}
  788. @classmethod
  789. def generate_api_tool_icon_url(cls, tenant_id: str, provider_id: str) -> dict:
  790. try:
  791. api_provider: ApiToolProvider | None = (
  792. db.session.query(ApiToolProvider)
  793. .where(ApiToolProvider.tenant_id == tenant_id, ApiToolProvider.id == provider_id)
  794. .first()
  795. )
  796. if api_provider is None:
  797. raise ToolProviderNotFoundError(f"api provider {provider_id} not found")
  798. icon: dict = json.loads(api_provider.icon)
  799. return icon
  800. except Exception:
  801. return {"background": "#252525", "content": "\ud83d\ude01"}
  802. @classmethod
  803. def generate_mcp_tool_icon_url(cls, tenant_id: str, provider_id: str) -> dict[str, str] | str:
  804. try:
  805. mcp_provider: MCPToolProvider | None = (
  806. db.session.query(MCPToolProvider)
  807. .where(MCPToolProvider.tenant_id == tenant_id, MCPToolProvider.server_identifier == provider_id)
  808. .first()
  809. )
  810. if mcp_provider is None:
  811. raise ToolProviderNotFoundError(f"mcp provider {provider_id} not found")
  812. return mcp_provider.provider_icon
  813. except Exception:
  814. return {"background": "#252525", "content": "\ud83d\ude01"}
  815. @classmethod
  816. def get_tool_icon(
  817. cls,
  818. tenant_id: str,
  819. provider_type: ToolProviderType,
  820. provider_id: str,
  821. ) -> Union[str, dict]:
  822. """
  823. get the tool icon
  824. :param tenant_id: the id of the tenant
  825. :param provider_type: the type of the provider
  826. :param provider_id: the id of the provider
  827. :return:
  828. """
  829. provider_type = provider_type
  830. provider_id = provider_id
  831. if provider_type == ToolProviderType.BUILT_IN:
  832. provider = ToolManager.get_builtin_provider(provider_id, tenant_id)
  833. if isinstance(provider, PluginToolProviderController):
  834. try:
  835. return cls.generate_plugin_tool_icon_url(tenant_id, provider.entity.identity.icon)
  836. except Exception:
  837. return {"background": "#252525", "content": "\ud83d\ude01"}
  838. return cls.generate_builtin_tool_icon_url(provider_id)
  839. elif provider_type == ToolProviderType.API:
  840. return cls.generate_api_tool_icon_url(tenant_id, provider_id)
  841. elif provider_type == ToolProviderType.WORKFLOW:
  842. return cls.generate_workflow_tool_icon_url(tenant_id, provider_id)
  843. elif provider_type == ToolProviderType.PLUGIN:
  844. provider = ToolManager.get_plugin_provider(provider_id, tenant_id)
  845. if isinstance(provider, PluginToolProviderController):
  846. try:
  847. return cls.generate_plugin_tool_icon_url(tenant_id, provider.entity.identity.icon)
  848. except Exception:
  849. return {"background": "#252525", "content": "\ud83d\ude01"}
  850. raise ValueError(f"plugin provider {provider_id} not found")
  851. elif provider_type == ToolProviderType.MCP:
  852. return cls.generate_mcp_tool_icon_url(tenant_id, provider_id)
  853. else:
  854. raise ValueError(f"provider type {provider_type} not found")
  855. @classmethod
  856. def _convert_tool_parameters_type(
  857. cls,
  858. parameters: list[ToolParameter],
  859. variable_pool: Optional[VariablePool],
  860. tool_configurations: dict[str, Any],
  861. typ: Literal["agent", "workflow", "tool"] = "workflow",
  862. ) -> dict[str, Any]:
  863. """
  864. Convert tool parameters type
  865. """
  866. from core.workflow.nodes.tool.entities import ToolNodeData
  867. from core.workflow.nodes.tool.exc import ToolParameterError
  868. runtime_parameters = {}
  869. for parameter in parameters:
  870. if (
  871. parameter.type
  872. in {
  873. ToolParameter.ToolParameterType.SYSTEM_FILES,
  874. ToolParameter.ToolParameterType.FILE,
  875. ToolParameter.ToolParameterType.FILES,
  876. }
  877. and parameter.required
  878. and typ == "agent"
  879. ):
  880. raise ValueError(f"file type parameter {parameter.name} not supported in agent")
  881. # save tool parameter to tool entity memory
  882. if parameter.form == ToolParameter.ToolParameterForm.FORM:
  883. if variable_pool:
  884. config = tool_configurations.get(parameter.name, {})
  885. if not (config and isinstance(config, dict) and config.get("value") is not None):
  886. continue
  887. tool_input = ToolNodeData.ToolInput(**tool_configurations.get(parameter.name, {}))
  888. if tool_input.type == "variable":
  889. variable = variable_pool.get(tool_input.value)
  890. if variable is None:
  891. raise ToolParameterError(f"Variable {tool_input.value} does not exist")
  892. parameter_value = variable.value
  893. elif tool_input.type == "constant":
  894. parameter_value = tool_input.value
  895. elif tool_input.type == "mixed":
  896. segment_group = variable_pool.convert_template(str(tool_input.value))
  897. parameter_value = segment_group.text
  898. else:
  899. raise ToolParameterError(f"Unknown tool input type '{tool_input.type}'")
  900. runtime_parameters[parameter.name] = parameter_value
  901. else:
  902. value = parameter.init_frontend_parameter(tool_configurations.get(parameter.name))
  903. runtime_parameters[parameter.name] = value
  904. return runtime_parameters
  905. ToolManager.load_hardcoded_providers_cache()