Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

tools_transform_service.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import json
  2. import logging
  3. from typing import Any, Optional, Union, cast
  4. from yarl import URL
  5. from configs import dify_config
  6. from core.mcp.types import Tool as MCPTool
  7. from core.tools.__base.tool import Tool
  8. from core.tools.__base.tool_runtime import ToolRuntime
  9. from core.tools.builtin_tool.provider import BuiltinToolProviderController
  10. from core.tools.custom_tool.provider import ApiToolProviderController
  11. from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
  12. from core.tools.entities.common_entities import I18nObject
  13. from core.tools.entities.tool_bundle import ApiToolBundle
  14. from core.tools.entities.tool_entities import (
  15. ApiProviderAuthType,
  16. ToolParameter,
  17. ToolProviderType,
  18. )
  19. from core.tools.plugin_tool.provider import PluginToolProviderController
  20. from core.tools.utils.configuration import ProviderConfigEncrypter
  21. from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
  22. from core.tools.workflow_as_tool.tool import WorkflowTool
  23. from models.tools import ApiToolProvider, BuiltinToolProvider, MCPToolProvider, WorkflowToolProvider
  24. logger = logging.getLogger(__name__)
  25. class ToolTransformService:
  26. @classmethod
  27. def get_plugin_icon_url(cls, tenant_id: str, filename: str) -> str:
  28. url_prefix = (
  29. URL(dify_config.CONSOLE_API_URL or "/") / "console" / "api" / "workspaces" / "current" / "plugin" / "icon"
  30. )
  31. return str(url_prefix % {"tenant_id": tenant_id, "filename": filename})
  32. @classmethod
  33. def get_tool_provider_icon_url(cls, provider_type: str, provider_name: str, icon: str | dict) -> Union[str, dict]:
  34. """
  35. get tool provider icon url
  36. """
  37. url_prefix = (
  38. URL(dify_config.CONSOLE_API_URL or "/") / "console" / "api" / "workspaces" / "current" / "tool-provider"
  39. )
  40. if provider_type == ToolProviderType.BUILT_IN.value:
  41. return str(url_prefix / "builtin" / provider_name / "icon")
  42. elif provider_type in {ToolProviderType.API.value, ToolProviderType.WORKFLOW.value}:
  43. try:
  44. if isinstance(icon, str):
  45. return cast(dict, json.loads(icon))
  46. return icon
  47. except Exception:
  48. return {"background": "#252525", "content": "\ud83d\ude01"}
  49. elif provider_type == ToolProviderType.MCP.value:
  50. return icon
  51. return ""
  52. @staticmethod
  53. def repack_provider(tenant_id: str, provider: Union[dict, ToolProviderApiEntity]):
  54. """
  55. repack provider
  56. :param tenant_id: the tenant id
  57. :param provider: the provider dict
  58. """
  59. if isinstance(provider, dict) and "icon" in provider:
  60. provider["icon"] = ToolTransformService.get_tool_provider_icon_url(
  61. provider_type=provider["type"], provider_name=provider["name"], icon=provider["icon"]
  62. )
  63. elif isinstance(provider, ToolProviderApiEntity):
  64. if provider.plugin_id:
  65. if isinstance(provider.icon, str):
  66. provider.icon = ToolTransformService.get_plugin_icon_url(
  67. tenant_id=tenant_id, filename=provider.icon
  68. )
  69. else:
  70. provider.icon = ToolTransformService.get_tool_provider_icon_url(
  71. provider_type=provider.type.value, provider_name=provider.name, icon=provider.icon
  72. )
  73. @classmethod
  74. def builtin_provider_to_user_provider(
  75. cls,
  76. provider_controller: BuiltinToolProviderController | PluginToolProviderController,
  77. db_provider: Optional[BuiltinToolProvider],
  78. decrypt_credentials: bool = True,
  79. ) -> ToolProviderApiEntity:
  80. """
  81. convert provider controller to user provider
  82. """
  83. result = ToolProviderApiEntity(
  84. id=provider_controller.entity.identity.name,
  85. author=provider_controller.entity.identity.author,
  86. name=provider_controller.entity.identity.name,
  87. description=provider_controller.entity.identity.description,
  88. icon=provider_controller.entity.identity.icon,
  89. label=provider_controller.entity.identity.label,
  90. type=ToolProviderType.BUILT_IN,
  91. masked_credentials={},
  92. is_team_authorization=False,
  93. plugin_id=None,
  94. tools=[],
  95. labels=provider_controller.tool_labels,
  96. )
  97. if isinstance(provider_controller, PluginToolProviderController):
  98. result.plugin_id = provider_controller.plugin_id
  99. result.plugin_unique_identifier = provider_controller.plugin_unique_identifier
  100. # get credentials schema
  101. schema = {x.to_basic_provider_config().name: x for x in provider_controller.get_credentials_schema()}
  102. for name, value in schema.items():
  103. if result.masked_credentials:
  104. result.masked_credentials[name] = ""
  105. # check if the provider need credentials
  106. if not provider_controller.need_credentials:
  107. result.is_team_authorization = True
  108. result.allow_delete = False
  109. elif db_provider:
  110. result.is_team_authorization = True
  111. if decrypt_credentials:
  112. credentials = db_provider.credentials
  113. # init tool configuration
  114. tool_configuration = ProviderConfigEncrypter(
  115. tenant_id=db_provider.tenant_id,
  116. config=[x.to_basic_provider_config() for x in provider_controller.get_credentials_schema()],
  117. provider_type=provider_controller.provider_type.value,
  118. provider_identity=provider_controller.entity.identity.name,
  119. )
  120. # decrypt the credentials and mask the credentials
  121. decrypted_credentials = tool_configuration.decrypt(data=credentials)
  122. masked_credentials = tool_configuration.mask_tool_credentials(data=decrypted_credentials)
  123. result.masked_credentials = masked_credentials
  124. result.original_credentials = decrypted_credentials
  125. return result
  126. @staticmethod
  127. def api_provider_to_controller(
  128. db_provider: ApiToolProvider,
  129. ) -> ApiToolProviderController:
  130. """
  131. convert provider controller to user provider
  132. """
  133. # package tool provider controller
  134. controller = ApiToolProviderController.from_db(
  135. db_provider=db_provider,
  136. auth_type=ApiProviderAuthType.API_KEY
  137. if db_provider.credentials["auth_type"] == "api_key"
  138. else ApiProviderAuthType.NONE,
  139. )
  140. return controller
  141. @staticmethod
  142. def workflow_provider_to_controller(db_provider: WorkflowToolProvider) -> WorkflowToolProviderController:
  143. """
  144. convert provider controller to provider
  145. """
  146. return WorkflowToolProviderController.from_db(db_provider)
  147. @staticmethod
  148. def workflow_provider_to_user_provider(
  149. provider_controller: WorkflowToolProviderController, labels: list[str] | None = None
  150. ):
  151. """
  152. convert provider controller to user provider
  153. """
  154. return ToolProviderApiEntity(
  155. id=provider_controller.provider_id,
  156. author=provider_controller.entity.identity.author,
  157. name=provider_controller.entity.identity.name,
  158. description=provider_controller.entity.identity.description,
  159. icon=provider_controller.entity.identity.icon,
  160. label=provider_controller.entity.identity.label,
  161. type=ToolProviderType.WORKFLOW,
  162. masked_credentials={},
  163. is_team_authorization=True,
  164. plugin_id=None,
  165. plugin_unique_identifier=None,
  166. tools=[],
  167. labels=labels or [],
  168. )
  169. @staticmethod
  170. def mcp_provider_to_user_provider(db_provider: MCPToolProvider, for_list: bool = False) -> ToolProviderApiEntity:
  171. user = db_provider.load_user()
  172. return ToolProviderApiEntity(
  173. id=db_provider.server_identifier if not for_list else db_provider.id,
  174. author=user.name if user else "Anonymous",
  175. name=db_provider.name,
  176. icon=db_provider.provider_icon,
  177. type=ToolProviderType.MCP,
  178. is_team_authorization=db_provider.authed,
  179. server_url=db_provider.masked_server_url,
  180. tools=ToolTransformService.mcp_tool_to_user_tool(
  181. db_provider, [MCPTool(**tool) for tool in json.loads(db_provider.tools)]
  182. ),
  183. updated_at=int(db_provider.updated_at.timestamp()),
  184. label=I18nObject(en_US=db_provider.name, zh_Hans=db_provider.name),
  185. description=I18nObject(en_US="", zh_Hans=""),
  186. server_identifier=db_provider.server_identifier,
  187. )
  188. @staticmethod
  189. def mcp_tool_to_user_tool(mcp_provider: MCPToolProvider, tools: list[MCPTool]) -> list[ToolApiEntity]:
  190. user = mcp_provider.load_user()
  191. return [
  192. ToolApiEntity(
  193. author=user.name if user else "Anonymous",
  194. name=tool.name,
  195. label=I18nObject(en_US=tool.name, zh_Hans=tool.name),
  196. description=I18nObject(en_US=tool.description, zh_Hans=tool.description),
  197. parameters=ToolTransformService.convert_mcp_schema_to_parameter(tool.inputSchema),
  198. labels=[],
  199. )
  200. for tool in tools
  201. ]
  202. @classmethod
  203. def api_provider_to_user_provider(
  204. cls,
  205. provider_controller: ApiToolProviderController,
  206. db_provider: ApiToolProvider,
  207. decrypt_credentials: bool = True,
  208. labels: list[str] | None = None,
  209. ) -> ToolProviderApiEntity:
  210. """
  211. convert provider controller to user provider
  212. """
  213. username = "Anonymous"
  214. if db_provider.user is None:
  215. raise ValueError(f"user is None for api provider {db_provider.id}")
  216. try:
  217. user = db_provider.user
  218. if not user:
  219. raise ValueError("user not found")
  220. username = user.name
  221. except Exception:
  222. logger.exception(f"failed to get user name for api provider {db_provider.id}")
  223. # add provider into providers
  224. credentials = db_provider.credentials
  225. result = ToolProviderApiEntity(
  226. id=db_provider.id,
  227. author=username,
  228. name=db_provider.name,
  229. description=I18nObject(
  230. en_US=db_provider.description,
  231. zh_Hans=db_provider.description,
  232. ),
  233. icon=db_provider.icon,
  234. label=I18nObject(
  235. en_US=db_provider.name,
  236. zh_Hans=db_provider.name,
  237. ),
  238. type=ToolProviderType.API,
  239. plugin_id=None,
  240. plugin_unique_identifier=None,
  241. masked_credentials={},
  242. is_team_authorization=True,
  243. tools=[],
  244. labels=labels or [],
  245. )
  246. if decrypt_credentials:
  247. # init tool configuration
  248. tool_configuration = ProviderConfigEncrypter(
  249. tenant_id=db_provider.tenant_id,
  250. config=[x.to_basic_provider_config() for x in provider_controller.get_credentials_schema()],
  251. provider_type=provider_controller.provider_type.value,
  252. provider_identity=provider_controller.entity.identity.name,
  253. )
  254. # decrypt the credentials and mask the credentials
  255. decrypted_credentials = tool_configuration.decrypt(data=credentials)
  256. masked_credentials = tool_configuration.mask_tool_credentials(data=decrypted_credentials)
  257. result.masked_credentials = masked_credentials
  258. return result
  259. @staticmethod
  260. def convert_tool_entity_to_api_entity(
  261. tool: Union[ApiToolBundle, WorkflowTool, Tool],
  262. tenant_id: str,
  263. credentials: dict | None = None,
  264. labels: list[str] | None = None,
  265. ) -> ToolApiEntity:
  266. """
  267. convert tool to user tool
  268. """
  269. if isinstance(tool, Tool):
  270. # fork tool runtime
  271. tool = tool.fork_tool_runtime(
  272. runtime=ToolRuntime(
  273. credentials=credentials or {},
  274. tenant_id=tenant_id,
  275. )
  276. )
  277. # get tool parameters
  278. parameters = tool.entity.parameters or []
  279. # get tool runtime parameters
  280. runtime_parameters = tool.get_runtime_parameters()
  281. # override parameters
  282. current_parameters = parameters.copy()
  283. for runtime_parameter in runtime_parameters:
  284. found = False
  285. for index, parameter in enumerate(current_parameters):
  286. if parameter.name == runtime_parameter.name and parameter.form == runtime_parameter.form:
  287. current_parameters[index] = runtime_parameter
  288. found = True
  289. break
  290. if not found and runtime_parameter.form == ToolParameter.ToolParameterForm.FORM:
  291. current_parameters.append(runtime_parameter)
  292. return ToolApiEntity(
  293. author=tool.entity.identity.author,
  294. name=tool.entity.identity.name,
  295. label=tool.entity.identity.label,
  296. description=tool.entity.description.human if tool.entity.description else I18nObject(en_US=""),
  297. output_schema=tool.entity.output_schema,
  298. parameters=current_parameters,
  299. labels=labels or [],
  300. )
  301. if isinstance(tool, ApiToolBundle):
  302. return ToolApiEntity(
  303. author=tool.author,
  304. name=tool.operation_id or "",
  305. label=I18nObject(en_US=tool.operation_id, zh_Hans=tool.operation_id),
  306. description=I18nObject(en_US=tool.summary or "", zh_Hans=tool.summary or ""),
  307. parameters=tool.parameters,
  308. labels=labels or [],
  309. )
  310. @staticmethod
  311. def convert_mcp_schema_to_parameter(schema: dict) -> list["ToolParameter"]:
  312. """
  313. Convert MCP JSON schema to tool parameters
  314. :param schema: JSON schema dictionary
  315. :return: list of ToolParameter instances
  316. """
  317. def create_parameter(
  318. name: str, description: str, param_type: str, required: bool, input_schema: dict | None = None
  319. ) -> ToolParameter:
  320. """Create a ToolParameter instance with given attributes"""
  321. input_schema_dict: dict[str, Any] = {"input_schema": input_schema} if input_schema else {}
  322. return ToolParameter(
  323. name=name,
  324. llm_description=description,
  325. label=I18nObject(en_US=name),
  326. form=ToolParameter.ToolParameterForm.LLM,
  327. required=required,
  328. type=ToolParameter.ToolParameterType(param_type),
  329. human_description=I18nObject(en_US=description),
  330. **input_schema_dict,
  331. )
  332. def process_properties(props: dict, required: list, prefix: str = "") -> list[ToolParameter]:
  333. """Process properties recursively"""
  334. TYPE_MAPPING = {"integer": "number", "float": "number"}
  335. COMPLEX_TYPES = ["array", "object"]
  336. parameters = []
  337. for name, prop in props.items():
  338. current_description = prop.get("description", "")
  339. prop_type = prop.get("type", "string")
  340. if isinstance(prop_type, list):
  341. prop_type = prop_type[0]
  342. if prop_type in TYPE_MAPPING:
  343. prop_type = TYPE_MAPPING[prop_type]
  344. input_schema = prop if prop_type in COMPLEX_TYPES else None
  345. parameters.append(
  346. create_parameter(name, current_description, prop_type, name in required, input_schema)
  347. )
  348. return parameters
  349. if schema.get("type") == "object" and "properties" in schema:
  350. return process_properties(schema["properties"], schema.get("required", []))
  351. return []