tool.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. from abc import ABC, abstractmethod
  2. from collections.abc import Mapping
  3. from copy import deepcopy
  4. from enum import Enum
  5. from typing import Any, Optional, Union
  6. from pydantic import BaseModel, ConfigDict, field_validator
  7. from pydantic_core.core_schema import ValidationInfo
  8. from core.app.entities.app_invoke_entities import InvokeFrom
  9. from core.file.file_obj import FileVar
  10. from core.tools.entities.tool_entities import (
  11. ToolDescription,
  12. ToolIdentity,
  13. ToolInvokeFrom,
  14. ToolInvokeMessage,
  15. ToolParameter,
  16. ToolProviderType,
  17. ToolRuntimeImageVariable,
  18. ToolRuntimeVariable,
  19. ToolRuntimeVariablePool,
  20. )
  21. from core.tools.tool_file_manager import ToolFileManager
  22. from core.tools.utils.tool_parameter_converter import ToolParameterConverter
  23. class Tool(BaseModel, ABC):
  24. identity: Optional[ToolIdentity] = None
  25. parameters: Optional[list[ToolParameter]] = None
  26. description: Optional[ToolDescription] = None
  27. is_team_authorization: bool = False
  28. # pydantic configs
  29. model_config = ConfigDict(protected_namespaces=())
  30. @field_validator('parameters', mode='before')
  31. @classmethod
  32. def set_parameters(cls, v, validation_info: ValidationInfo) -> list[ToolParameter]:
  33. return v or []
  34. class Runtime(BaseModel):
  35. """
  36. Meta data of a tool call processing
  37. """
  38. def __init__(self, **data: Any):
  39. super().__init__(**data)
  40. if not self.runtime_parameters:
  41. self.runtime_parameters = {}
  42. tenant_id: Optional[str] = None
  43. tool_id: Optional[str] = None
  44. invoke_from: Optional[InvokeFrom] = None
  45. tool_invoke_from: Optional[ToolInvokeFrom] = None
  46. credentials: Optional[dict[str, Any]] = None
  47. runtime_parameters: Optional[dict[str, Any]] = None
  48. runtime: Optional[Runtime] = None
  49. variables: Optional[ToolRuntimeVariablePool] = None
  50. def __init__(self, **data: Any):
  51. super().__init__(**data)
  52. class VARIABLE_KEY(Enum):
  53. IMAGE = 'image'
  54. def fork_tool_runtime(self, runtime: dict[str, Any]) -> 'Tool':
  55. """
  56. fork a new tool with meta data
  57. :param meta: the meta data of a tool call processing, tenant_id is required
  58. :return: the new tool
  59. """
  60. return self.__class__(
  61. identity=self.identity.model_copy() if self.identity else None,
  62. parameters=self.parameters.copy() if self.parameters else None,
  63. description=self.description.model_copy() if self.description else None,
  64. runtime=Tool.Runtime(**runtime),
  65. )
  66. @abstractmethod
  67. def tool_provider_type(self) -> ToolProviderType:
  68. """
  69. get the tool provider type
  70. :return: the tool provider type
  71. """
  72. def load_variables(self, variables: ToolRuntimeVariablePool):
  73. """
  74. load variables from database
  75. :param conversation_id: the conversation id
  76. """
  77. self.variables = variables
  78. def set_image_variable(self, variable_name: str, image_key: str) -> None:
  79. """
  80. set an image variable
  81. """
  82. if not self.variables:
  83. return
  84. self.variables.set_file(self.identity.name, variable_name, image_key)
  85. def set_text_variable(self, variable_name: str, text: str) -> None:
  86. """
  87. set a text variable
  88. """
  89. if not self.variables:
  90. return
  91. self.variables.set_text(self.identity.name, variable_name, text)
  92. def get_variable(self, name: Union[str, Enum]) -> Optional[ToolRuntimeVariable]:
  93. """
  94. get a variable
  95. :param name: the name of the variable
  96. :return: the variable
  97. """
  98. if not self.variables:
  99. return None
  100. if isinstance(name, Enum):
  101. name = name.value
  102. for variable in self.variables.pool:
  103. if variable.name == name:
  104. return variable
  105. return None
  106. def get_default_image_variable(self) -> Optional[ToolRuntimeVariable]:
  107. """
  108. get the default image variable
  109. :return: the image variable
  110. """
  111. if not self.variables:
  112. return None
  113. return self.get_variable(self.VARIABLE_KEY.IMAGE)
  114. def get_variable_file(self, name: Union[str, Enum]) -> Optional[bytes]:
  115. """
  116. get a variable file
  117. :param name: the name of the variable
  118. :return: the variable file
  119. """
  120. variable = self.get_variable(name)
  121. if not variable:
  122. return None
  123. if not isinstance(variable, ToolRuntimeImageVariable):
  124. return None
  125. message_file_id = variable.value
  126. # get file binary
  127. file_binary = ToolFileManager.get_file_binary_by_message_file_id(message_file_id)
  128. if not file_binary:
  129. return None
  130. return file_binary[0]
  131. def list_variables(self) -> list[ToolRuntimeVariable]:
  132. """
  133. list all variables
  134. :return: the variables
  135. """
  136. if not self.variables:
  137. return []
  138. return self.variables.pool
  139. def list_default_image_variables(self) -> list[ToolRuntimeVariable]:
  140. """
  141. list all image variables
  142. :return: the image variables
  143. """
  144. if not self.variables:
  145. return []
  146. result = []
  147. for variable in self.variables.pool:
  148. if variable.name.startswith(self.VARIABLE_KEY.IMAGE.value):
  149. result.append(variable)
  150. return result
  151. def invoke(self, user_id: str, tool_parameters: Mapping[str, Any]) -> list[ToolInvokeMessage]:
  152. # update tool_parameters
  153. # TODO: Fix type error.
  154. if self.runtime.runtime_parameters:
  155. tool_parameters.update(self.runtime.runtime_parameters)
  156. # try parse tool parameters into the correct type
  157. tool_parameters = self._transform_tool_parameters_type(tool_parameters)
  158. result = self._invoke(
  159. user_id=user_id,
  160. tool_parameters=tool_parameters,
  161. )
  162. if not isinstance(result, list):
  163. result = [result]
  164. return result
  165. def _transform_tool_parameters_type(self, tool_parameters: Mapping[str, Any]) -> dict[str, Any]:
  166. """
  167. Transform tool parameters type
  168. """
  169. # Temp fix for the issue that the tool parameters will be converted to empty while validating the credentials
  170. result = deepcopy(tool_parameters)
  171. for parameter in self.parameters or []:
  172. if parameter.name in tool_parameters:
  173. result[parameter.name] = ToolParameterConverter.cast_parameter_by_type(tool_parameters[parameter.name], parameter.type)
  174. return result
  175. @abstractmethod
  176. def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
  177. pass
  178. def validate_credentials(self, credentials: dict[str, Any], parameters: dict[str, Any]) -> None:
  179. """
  180. validate the credentials
  181. :param credentials: the credentials
  182. :param parameters: the parameters
  183. """
  184. pass
  185. def get_runtime_parameters(self) -> list[ToolParameter]:
  186. """
  187. get the runtime parameters
  188. interface for developer to dynamic change the parameters of a tool depends on the variables pool
  189. :return: the runtime parameters
  190. """
  191. return self.parameters or []
  192. def get_all_runtime_parameters(self) -> list[ToolParameter]:
  193. """
  194. get all runtime parameters
  195. :return: all runtime parameters
  196. """
  197. parameters = self.parameters or []
  198. parameters = parameters.copy()
  199. user_parameters = self.get_runtime_parameters() or []
  200. user_parameters = user_parameters.copy()
  201. # override parameters
  202. for parameter in user_parameters:
  203. # check if parameter in tool parameters
  204. found = False
  205. for tool_parameter in parameters:
  206. if tool_parameter.name == parameter.name:
  207. found = True
  208. break
  209. if found:
  210. # override parameter
  211. tool_parameter.type = parameter.type
  212. tool_parameter.form = parameter.form
  213. tool_parameter.required = parameter.required
  214. tool_parameter.default = parameter.default
  215. tool_parameter.options = parameter.options
  216. tool_parameter.llm_description = parameter.llm_description
  217. else:
  218. # add new parameter
  219. parameters.append(parameter)
  220. return parameters
  221. def create_image_message(self, image: str, save_as: str = '') -> ToolInvokeMessage:
  222. """
  223. create an image message
  224. :param image: the url of the image
  225. :return: the image message
  226. """
  227. return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.IMAGE,
  228. message=image,
  229. save_as=save_as)
  230. def create_file_var_message(self, file_var: FileVar) -> ToolInvokeMessage:
  231. return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.FILE_VAR,
  232. message='',
  233. meta={
  234. 'file_var': file_var
  235. },
  236. save_as='')
  237. def create_link_message(self, link: str, save_as: str = '') -> ToolInvokeMessage:
  238. """
  239. create a link message
  240. :param link: the url of the link
  241. :return: the link message
  242. """
  243. return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.LINK,
  244. message=link,
  245. save_as=save_as)
  246. def create_text_message(self, text: str, save_as: str = '') -> ToolInvokeMessage:
  247. """
  248. create a text message
  249. :param text: the text
  250. :return: the text message
  251. """
  252. return ToolInvokeMessage(
  253. type=ToolInvokeMessage.MessageType.TEXT,
  254. message=text,
  255. save_as=save_as
  256. )
  257. def create_blob_message(self, blob: bytes, meta: dict = None, save_as: str = '') -> ToolInvokeMessage:
  258. """
  259. create a blob message
  260. :param blob: the blob
  261. :return: the blob message
  262. """
  263. return ToolInvokeMessage(
  264. type=ToolInvokeMessage.MessageType.BLOB,
  265. message=blob, meta=meta,
  266. save_as=save_as
  267. )
  268. def create_json_message(self, object: dict) -> ToolInvokeMessage:
  269. """
  270. create a json message
  271. """
  272. return ToolInvokeMessage(
  273. type=ToolInvokeMessage.MessageType.JSON,
  274. message=object
  275. )