tool.py 12 KB

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