You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

file_manager.py 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import base64
  2. from configs import dify_config
  3. from core.model_runtime.entities.message_entities import ImagePromptMessageContent
  4. from extensions.ext_database import db
  5. from extensions.ext_storage import storage
  6. from models import UploadFile
  7. from . import helpers
  8. from .enums import FileAttribute
  9. from .models import File, FileTransferMethod, FileType
  10. from .tool_file_parser import ToolFileParser
  11. def get_attr(*, file: "File", attr: "FileAttribute"):
  12. match attr:
  13. case FileAttribute.TYPE:
  14. return file.type.value
  15. case FileAttribute.SIZE:
  16. return file.size
  17. case FileAttribute.NAME:
  18. return file.filename
  19. case FileAttribute.MIME_TYPE:
  20. return file.mime_type
  21. case FileAttribute.TRANSFER_METHOD:
  22. return file.transfer_method.value
  23. case FileAttribute.URL:
  24. return file.remote_url
  25. case FileAttribute.EXTENSION:
  26. return file.extension
  27. case _:
  28. raise ValueError(f"Invalid file attribute: {attr}")
  29. def to_prompt_message_content(file: "File", /):
  30. """
  31. Convert a File object to an ImagePromptMessageContent object.
  32. This function takes a File object and converts it to an ImagePromptMessageContent
  33. object, which can be used as a prompt for image-based AI models.
  34. Args:
  35. file (File): The File object to convert. Must be of type FileType.IMAGE.
  36. Returns:
  37. ImagePromptMessageContent: An object containing the image data and detail level.
  38. Raises:
  39. ValueError: If the file is not an image or if the file data is missing.
  40. Note:
  41. The detail level of the image prompt is determined by the file's extra_config.
  42. If not specified, it defaults to ImagePromptMessageContent.DETAIL.LOW.
  43. """
  44. if file.type != FileType.IMAGE:
  45. raise ValueError("Only image file can convert to prompt message content")
  46. url_or_b64_data = _get_url_or_b64_data(file=file)
  47. if url_or_b64_data is None:
  48. raise ValueError("Missing file data")
  49. # decide the detail of image prompt message content
  50. if file._extra_config and file._extra_config.image_config and file._extra_config.image_config.detail:
  51. detail = file._extra_config.image_config.detail
  52. else:
  53. detail = ImagePromptMessageContent.DETAIL.LOW
  54. return ImagePromptMessageContent(data=url_or_b64_data, detail=detail)
  55. def download(*, upload_file_id: str, tenant_id: str):
  56. upload_file = (
  57. db.session.query(UploadFile).filter(UploadFile.id == upload_file_id, UploadFile.tenant_id == tenant_id).first()
  58. )
  59. if not upload_file:
  60. raise ValueError("upload file not found")
  61. return _download(upload_file.key)
  62. def _download(path: str, /):
  63. """
  64. Download and return the contents of a file as bytes.
  65. This function loads the file from storage and ensures it's in bytes format.
  66. Args:
  67. path (str): The path to the file in storage.
  68. Returns:
  69. bytes: The contents of the file as a bytes object.
  70. Raises:
  71. ValueError: If the loaded file is not a bytes object.
  72. """
  73. data = storage.load(path, stream=False)
  74. if not isinstance(data, bytes):
  75. raise ValueError(f"file {path} is not a bytes object")
  76. return data
  77. def _get_base64(*, upload_file_id: str, tenant_id: str) -> str | None:
  78. upload_file = (
  79. db.session.query(UploadFile).filter(UploadFile.id == upload_file_id, UploadFile.tenant_id == tenant_id).first()
  80. )
  81. if not upload_file:
  82. return None
  83. data = _download(upload_file.key)
  84. if data is None:
  85. return None
  86. encoded_string = base64.b64encode(data).decode("utf-8")
  87. return f"data:{upload_file.mime_type};base64,{encoded_string}"
  88. def _get_url_or_b64_data(file: "File"):
  89. if file.type == FileType.IMAGE:
  90. if file.transfer_method == FileTransferMethod.REMOTE_URL:
  91. return file.remote_url
  92. elif file.transfer_method == FileTransferMethod.LOCAL_FILE:
  93. if file.related_id is None:
  94. raise ValueError("Missing file related_id")
  95. if dify_config.MULTIMODAL_SEND_IMAGE_FORMAT == "url":
  96. return helpers.get_signed_image_url(upload_file_id=file.related_id)
  97. return _get_base64(upload_file_id=file.related_id, tenant_id=file.tenant_id)
  98. elif file.transfer_method == FileTransferMethod.TOOL_FILE:
  99. # add sign url
  100. if file.related_id is None or file.extension is None:
  101. raise ValueError("Missing file related_id or extension")
  102. return ToolFileParser.get_tool_file_manager().sign_file(
  103. tool_file_id=file.related_id, extension=file.extension
  104. )