| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 | import loggingfrom collections.abc import Generatorfrom mimetypes import guess_extensionfrom typing import Optionalfrom core.file import File, FileTransferMethod, FileTypefrom core.tools.entities.tool_entities import ToolInvokeMessagefrom core.tools.tool_file_manager import ToolFileManagerlogger = logging.getLogger(__name__)class ToolFileMessageTransformer:    @classmethod    def transform_tool_invoke_messages(        cls,        messages: Generator[ToolInvokeMessage, None, None],        user_id: str,        tenant_id: str,        conversation_id: Optional[str] = None,    ) -> Generator[ToolInvokeMessage, None, None]:        """        Transform tool message and handle file download        """        for message in messages:            if message.type in {ToolInvokeMessage.MessageType.TEXT, ToolInvokeMessage.MessageType.LINK}:                yield message            elif message.type == ToolInvokeMessage.MessageType.IMAGE and isinstance(                message.message, ToolInvokeMessage.TextMessage            ):                # try to download image                try:                    assert isinstance(message.message, ToolInvokeMessage.TextMessage)                    file = ToolFileManager.create_file_by_url(                        user_id=user_id,                        tenant_id=tenant_id,                        file_url=message.message.text,                        conversation_id=conversation_id,                    )                    url = f"/files/tools/{file.id}{guess_extension(file.mimetype) or '.png'}"                    yield ToolInvokeMessage(                        type=ToolInvokeMessage.MessageType.IMAGE_LINK,                        message=ToolInvokeMessage.TextMessage(text=url),                        meta=message.meta.copy() if message.meta is not None else {},                    )                except Exception as e:                    yield ToolInvokeMessage(                        type=ToolInvokeMessage.MessageType.TEXT,                        message=ToolInvokeMessage.TextMessage(                            text=f"Failed to download image: {message.message.text}: {e}"                        ),                        meta=message.meta.copy() if message.meta is not None else {},                    )            elif message.type == ToolInvokeMessage.MessageType.BLOB:                # get mime type and save blob to storage                meta = message.meta or {}                mimetype = meta.get("mime_type", "application/octet-stream")                # get filename from meta                filename = meta.get("file_name", None)                # if message is str, encode it to bytes                if not isinstance(message.message, ToolInvokeMessage.BlobMessage):                    raise ValueError("unexpected message type")                # FIXME: should do a type check here.                assert isinstance(message.message.blob, bytes)                file = ToolFileManager.create_file_by_raw(                    user_id=user_id,                    tenant_id=tenant_id,                    conversation_id=conversation_id,                    file_binary=message.message.blob,                    mimetype=mimetype,                    filename=filename,                )                url = cls.get_tool_file_url(tool_file_id=file.id, extension=guess_extension(file.mimetype))                # check if file is image                if "image" in mimetype:                    yield ToolInvokeMessage(                        type=ToolInvokeMessage.MessageType.IMAGE_LINK,                        message=ToolInvokeMessage.TextMessage(text=url),                        meta=meta.copy() if meta is not None else {},                    )                else:                    yield ToolInvokeMessage(                        type=ToolInvokeMessage.MessageType.BINARY_LINK,                        message=ToolInvokeMessage.TextMessage(text=url),                        meta=meta.copy() if meta is not None else {},                    )            elif message.type == ToolInvokeMessage.MessageType.FILE:                meta = message.meta or {}                file = meta.get("file", None)                if isinstance(file, File):                    if file.transfer_method == FileTransferMethod.TOOL_FILE:                        assert file.related_id is not None                        url = cls.get_tool_file_url(tool_file_id=file.related_id, extension=file.extension)                        if file.type == FileType.IMAGE:                            yield ToolInvokeMessage(                                type=ToolInvokeMessage.MessageType.IMAGE_LINK,                                message=ToolInvokeMessage.TextMessage(text=url),                                meta=meta.copy() if meta is not None else {},                            )                        else:                            yield ToolInvokeMessage(                                type=ToolInvokeMessage.MessageType.LINK,                                message=ToolInvokeMessage.TextMessage(text=url),                                meta=meta.copy() if meta is not None else {},                            )                    else:                        yield message            else:                yield message    @classmethod    def get_tool_file_url(cls, tool_file_id: str, extension: Optional[str]) -> str:        return f"/files/tools/{tool_file_id}{extension or '.bin'}"
 |