workflow_tool.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import json
  2. import logging
  3. from copy import deepcopy
  4. from typing import Any, Optional, Union
  5. from core.file.file_obj import FileTransferMethod, FileVar
  6. from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter, ToolProviderType
  7. from core.tools.tool.tool import Tool
  8. from extensions.ext_database import db
  9. from models.account import Account
  10. from models.model import App, EndUser
  11. from models.workflow import Workflow
  12. logger = logging.getLogger(__name__)
  13. class WorkflowTool(Tool):
  14. workflow_app_id: str
  15. version: str
  16. workflow_entities: dict[str, Any]
  17. workflow_call_depth: int
  18. thread_pool_id: Optional[str] = None
  19. label: str
  20. """
  21. Workflow tool.
  22. """
  23. def tool_provider_type(self) -> ToolProviderType:
  24. """
  25. get the tool provider type
  26. :return: the tool provider type
  27. """
  28. return ToolProviderType.WORKFLOW
  29. def _invoke(
  30. self, user_id: str, tool_parameters: dict[str, Any]
  31. ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
  32. """
  33. invoke the tool
  34. """
  35. app = self._get_app(app_id=self.workflow_app_id)
  36. workflow = self._get_workflow(app_id=self.workflow_app_id, version=self.version)
  37. # transform the tool parameters
  38. tool_parameters, files = self._transform_args(tool_parameters)
  39. from core.app.apps.workflow.app_generator import WorkflowAppGenerator
  40. generator = WorkflowAppGenerator()
  41. result = generator.generate(
  42. app_model=app,
  43. workflow=workflow,
  44. user=self._get_user(user_id),
  45. args={"inputs": tool_parameters, "files": files},
  46. invoke_from=self.runtime.invoke_from,
  47. stream=False,
  48. call_depth=self.workflow_call_depth + 1,
  49. workflow_thread_pool_id=self.thread_pool_id,
  50. )
  51. data = result.get("data", {})
  52. if data.get("error"):
  53. raise Exception(data.get("error"))
  54. result = []
  55. outputs = data.get("outputs", {})
  56. outputs, files = self._extract_files(outputs)
  57. for file in files:
  58. result.append(self.create_file_var_message(file))
  59. result.append(self.create_text_message(json.dumps(outputs, ensure_ascii=False)))
  60. result.append(self.create_json_message(outputs))
  61. return result
  62. def _get_user(self, user_id: str) -> Union[EndUser, Account]:
  63. """
  64. get the user by user id
  65. """
  66. user = db.session.query(EndUser).filter(EndUser.id == user_id).first()
  67. if not user:
  68. user = db.session.query(Account).filter(Account.id == user_id).first()
  69. if not user:
  70. raise ValueError("user not found")
  71. return user
  72. def fork_tool_runtime(self, runtime: dict[str, Any]) -> "WorkflowTool":
  73. """
  74. fork a new tool with meta data
  75. :param meta: the meta data of a tool call processing, tenant_id is required
  76. :return: the new tool
  77. """
  78. return self.__class__(
  79. identity=deepcopy(self.identity),
  80. parameters=deepcopy(self.parameters),
  81. description=deepcopy(self.description),
  82. runtime=Tool.Runtime(**runtime),
  83. workflow_app_id=self.workflow_app_id,
  84. workflow_entities=self.workflow_entities,
  85. workflow_call_depth=self.workflow_call_depth,
  86. version=self.version,
  87. label=self.label,
  88. )
  89. def _get_workflow(self, app_id: str, version: str) -> Workflow:
  90. """
  91. get the workflow by app id and version
  92. """
  93. if not version:
  94. workflow = (
  95. db.session.query(Workflow)
  96. .filter(Workflow.app_id == app_id, Workflow.version != "draft")
  97. .order_by(Workflow.created_at.desc())
  98. .first()
  99. )
  100. else:
  101. workflow = db.session.query(Workflow).filter(Workflow.app_id == app_id, Workflow.version == version).first()
  102. if not workflow:
  103. raise ValueError("workflow not found or not published")
  104. return workflow
  105. def _get_app(self, app_id: str) -> App:
  106. """
  107. get the app by app id
  108. """
  109. app = db.session.query(App).filter(App.id == app_id).first()
  110. if not app:
  111. raise ValueError("app not found")
  112. return app
  113. def _transform_args(self, tool_parameters: dict) -> tuple[dict, list[dict]]:
  114. """
  115. transform the tool parameters
  116. :param tool_parameters: the tool parameters
  117. :return: tool_parameters, files
  118. """
  119. parameter_rules = self.get_all_runtime_parameters()
  120. parameters_result = {}
  121. files = []
  122. for parameter in parameter_rules:
  123. if parameter.type == ToolParameter.ToolParameterType.FILE:
  124. file = tool_parameters.get(parameter.name)
  125. if file:
  126. try:
  127. file_var_list = [FileVar(**f) for f in file]
  128. for file_var in file_var_list:
  129. file_dict = {
  130. "transfer_method": file_var.transfer_method.value,
  131. "type": file_var.type.value,
  132. }
  133. if file_var.transfer_method == FileTransferMethod.TOOL_FILE:
  134. file_dict["tool_file_id"] = file_var.related_id
  135. elif file_var.transfer_method == FileTransferMethod.LOCAL_FILE:
  136. file_dict["upload_file_id"] = file_var.related_id
  137. elif file_var.transfer_method == FileTransferMethod.REMOTE_URL:
  138. file_dict["url"] = file_var.preview_url
  139. files.append(file_dict)
  140. except Exception as e:
  141. logger.exception(e)
  142. else:
  143. parameters_result[parameter.name] = tool_parameters.get(parameter.name)
  144. return parameters_result, files
  145. def _extract_files(self, outputs: dict) -> tuple[dict, list[FileVar]]:
  146. """
  147. extract files from the result
  148. :param result: the result
  149. :return: the result, files
  150. """
  151. files = []
  152. result = {}
  153. for key, value in outputs.items():
  154. if isinstance(value, list):
  155. has_file = False
  156. for item in value:
  157. if isinstance(item, dict) and item.get("__variant") == "FileVar":
  158. try:
  159. files.append(FileVar(**item))
  160. has_file = True
  161. except Exception as e:
  162. pass
  163. if has_file:
  164. continue
  165. result[key] = value
  166. return result, files