message_transformer.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import logging
  2. from collections.abc import Generator
  3. from mimetypes import guess_extension
  4. from typing import Optional
  5. from core.file import File, FileTransferMethod, FileType
  6. from core.tools.entities.tool_entities import ToolInvokeMessage
  7. from core.tools.tool_file_manager import ToolFileManager
  8. logger = logging.getLogger(__name__)
  9. class ToolFileMessageTransformer:
  10. @classmethod
  11. def transform_tool_invoke_messages(
  12. cls,
  13. messages: Generator[ToolInvokeMessage, None, None],
  14. user_id: str,
  15. tenant_id: str,
  16. conversation_id: Optional[str] = None,
  17. ) -> Generator[ToolInvokeMessage, None, None]:
  18. """
  19. Transform tool message and handle file download
  20. """
  21. for message in messages:
  22. if message.type in {ToolInvokeMessage.MessageType.TEXT, ToolInvokeMessage.MessageType.LINK}:
  23. yield message
  24. elif message.type == ToolInvokeMessage.MessageType.IMAGE and isinstance(
  25. message.message, ToolInvokeMessage.TextMessage
  26. ):
  27. # try to download image
  28. try:
  29. if not conversation_id:
  30. raise
  31. assert isinstance(message.message, ToolInvokeMessage.TextMessage)
  32. file = ToolFileManager.create_file_by_url(
  33. user_id=user_id,
  34. tenant_id=tenant_id,
  35. file_url=message.message.text,
  36. conversation_id=conversation_id,
  37. )
  38. url = f'/files/tools/{file.id}{guess_extension(file.mimetype) or ".png"}'
  39. yield ToolInvokeMessage(
  40. type=ToolInvokeMessage.MessageType.IMAGE_LINK,
  41. message=ToolInvokeMessage.TextMessage(text=url),
  42. save_as=message.save_as,
  43. meta=message.meta.copy() if message.meta is not None else {},
  44. )
  45. except Exception as e:
  46. yield ToolInvokeMessage(
  47. type=ToolInvokeMessage.MessageType.TEXT,
  48. message=ToolInvokeMessage.TextMessage(
  49. text=f"Failed to download image: {message.message.text}: {e}"
  50. ),
  51. meta=message.meta.copy() if message.meta is not None else {},
  52. save_as=message.save_as,
  53. )
  54. elif message.type == ToolInvokeMessage.MessageType.BLOB:
  55. # get mime type and save blob to storage
  56. assert message.meta
  57. mimetype = message.meta.get("mime_type", "octet/stream")
  58. # if message is str, encode it to bytes
  59. if not isinstance(message.message, ToolInvokeMessage.BlobMessage):
  60. raise ValueError("unexpected message type")
  61. # FIXME: should do a type check here.
  62. assert isinstance(message.message.blob, bytes)
  63. file = ToolFileManager.create_file_by_raw(
  64. user_id=user_id,
  65. tenant_id=tenant_id,
  66. conversation_id=conversation_id,
  67. file_binary=message.message.blob,
  68. mimetype=mimetype,
  69. )
  70. url = cls.get_tool_file_url(tool_file_id=file.id, extension=guess_extension(file.mimetype))
  71. # check if file is image
  72. if "image" in mimetype:
  73. yield ToolInvokeMessage(
  74. type=ToolInvokeMessage.MessageType.IMAGE_LINK,
  75. message=ToolInvokeMessage.TextMessage(text=url),
  76. save_as=message.save_as,
  77. meta=message.meta.copy() if message.meta is not None else {},
  78. )
  79. else:
  80. yield ToolInvokeMessage(
  81. type=ToolInvokeMessage.MessageType.LINK,
  82. message=ToolInvokeMessage.TextMessage(text=url),
  83. save_as=message.save_as,
  84. meta=message.meta.copy() if message.meta is not None else {},
  85. )
  86. elif message.type == ToolInvokeMessage.MessageType.FILE:
  87. assert message.meta is not None
  88. file = message.meta.get("file")
  89. if isinstance(file, File):
  90. if file.transfer_method == FileTransferMethod.TOOL_FILE:
  91. assert file.related_id is not None
  92. url = cls.get_tool_file_url(tool_file_id=file.related_id, extension=file.extension)
  93. if file.type == FileType.IMAGE:
  94. yield ToolInvokeMessage(
  95. type=ToolInvokeMessage.MessageType.IMAGE_LINK,
  96. message=ToolInvokeMessage.TextMessage(text=url),
  97. save_as=message.save_as,
  98. meta=message.meta.copy() if message.meta is not None else {},
  99. )
  100. else:
  101. yield ToolInvokeMessage(
  102. type=ToolInvokeMessage.MessageType.LINK,
  103. message=ToolInvokeMessage.TextMessage(text=url),
  104. save_as=message.save_as,
  105. meta=message.meta.copy() if message.meta is not None else {},
  106. )
  107. else:
  108. yield message
  109. else:
  110. yield message
  111. @classmethod
  112. def get_tool_file_url(cls, tool_file_id: str, extension: Optional[str]) -> str:
  113. return f'/files/tools/{tool_file_id}{extension or ".bin"}'