| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 | from abc import ABC, abstractmethodfrom collections.abc import Generatorfrom copy import deepcopyfrom typing import TYPE_CHECKING, Any, Optionalif TYPE_CHECKING:    from models.model import Filefrom core.tools.__base.tool_runtime import ToolRuntimefrom core.tools.entities.tool_entities import (    ToolEntity,    ToolInvokeMessage,    ToolParameter,    ToolProviderType,)class Tool(ABC):    """    The base class of a tool    """    entity: ToolEntity    runtime: ToolRuntime    def __init__(self, entity: ToolEntity, runtime: ToolRuntime) -> None:        self.entity = entity        self.runtime = runtime    def fork_tool_runtime(self, runtime: ToolRuntime) -> "Tool":        """        fork a new tool with meta data        :param meta: the meta data of a tool call processing, tenant_id is required        :return: the new tool        """        return self.__class__(            entity=self.entity.model_copy(),            runtime=runtime,        )    @abstractmethod    def tool_provider_type(self) -> ToolProviderType:        """        get the tool provider type        :return: the tool provider type        """    def invoke(        self,        user_id: str,        tool_parameters: dict[str, Any],        conversation_id: Optional[str] = None,        app_id: Optional[str] = None,        message_id: Optional[str] = None,    ) -> Generator[ToolInvokeMessage]:        if self.runtime and self.runtime.runtime_parameters:            tool_parameters.update(self.runtime.runtime_parameters)        # try parse tool parameters into the correct type        tool_parameters = self._transform_tool_parameters_type(tool_parameters)        result = self._invoke(            user_id=user_id,            tool_parameters=tool_parameters,            conversation_id=conversation_id,            app_id=app_id,            message_id=message_id,        )        if isinstance(result, ToolInvokeMessage):            def single_generator() -> Generator[ToolInvokeMessage, None, None]:                yield result            return single_generator()        elif isinstance(result, list):            def generator() -> Generator[ToolInvokeMessage, None, None]:                yield from result            return generator()        else:            return result    def _transform_tool_parameters_type(self, tool_parameters: dict[str, Any]) -> dict[str, Any]:        """        Transform tool parameters type        """        # Temp fix for the issue that the tool parameters will be converted to empty while validating the credentials        result = deepcopy(tool_parameters)        for parameter in self.entity.parameters or []:            if parameter.name in tool_parameters:                result[parameter.name] = parameter.type.cast_value(tool_parameters[parameter.name])        return result    @abstractmethod    def _invoke(        self,        user_id: str,        tool_parameters: dict[str, Any],        conversation_id: Optional[str] = None,        app_id: Optional[str] = None,        message_id: Optional[str] = None,    ) -> ToolInvokeMessage | list[ToolInvokeMessage] | Generator[ToolInvokeMessage, None, None]:        pass    def get_runtime_parameters(        self,        conversation_id: Optional[str] = None,        app_id: Optional[str] = None,        message_id: Optional[str] = None,    ) -> list[ToolParameter]:        """        get the runtime parameters        interface for developer to dynamic change the parameters of a tool depends on the variables pool        :return: the runtime parameters        """        return self.entity.parameters    def get_merged_runtime_parameters(        self,        conversation_id: Optional[str] = None,        app_id: Optional[str] = None,        message_id: Optional[str] = None,    ) -> list[ToolParameter]:        """        get merged runtime parameters        :return: merged runtime parameters        """        parameters = self.entity.parameters        parameters = parameters.copy()        user_parameters = self.get_runtime_parameters() or []        user_parameters = user_parameters.copy()        # override parameters        for parameter in user_parameters:            # check if parameter in tool parameters            for tool_parameter in parameters:                if tool_parameter.name == parameter.name:                    # override parameter                    tool_parameter.type = parameter.type                    tool_parameter.form = parameter.form                    tool_parameter.required = parameter.required                    tool_parameter.default = parameter.default                    tool_parameter.options = parameter.options                    tool_parameter.llm_description = parameter.llm_description                    break            else:                # add new parameter                parameters.append(parameter)        return parameters    def create_image_message(        self,        image: str,    ) -> ToolInvokeMessage:        """        create an image message        :param image: the url of the image        :return: the image message        """        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.IMAGE, message=ToolInvokeMessage.TextMessage(text=image)        )    def create_file_message(self, file: "File") -> ToolInvokeMessage:        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.FILE,            message=ToolInvokeMessage.FileMessage(),            meta={"file": file},        )    def create_link_message(self, link: str) -> ToolInvokeMessage:        """        create a link message        :param link: the url of the link        :return: the link message        """        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.LINK, message=ToolInvokeMessage.TextMessage(text=link)        )    def create_text_message(self, text: str) -> ToolInvokeMessage:        """        create a text message        :param text: the text        :return: the text message        """        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.TEXT,            message=ToolInvokeMessage.TextMessage(text=text),        )    def create_blob_message(self, blob: bytes, meta: Optional[dict] = None) -> ToolInvokeMessage:        """        create a blob message        :param blob: the blob        :return: the blob message        """        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.BLOB,            message=ToolInvokeMessage.BlobMessage(blob=blob),            meta=meta,        )    def create_json_message(self, object: dict) -> ToolInvokeMessage:        """        create a json message        """        return ToolInvokeMessage(            type=ToolInvokeMessage.MessageType.JSON, message=ToolInvokeMessage.JsonMessage(json_object=object)        )
 |