tool_manager.py 23 KB

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