file_manager.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import base64
  2. from configs import dify_config
  3. from core.file import file_repository
  4. from core.helper import ssrf_proxy
  5. from core.model_runtime.entities import AudioPromptMessageContent, ImagePromptMessageContent, VideoPromptMessageContent
  6. from extensions.ext_database import db
  7. from extensions.ext_storage import storage
  8. from . import helpers
  9. from .enums import FileAttribute
  10. from .models import File, FileTransferMethod, FileType
  11. from .tool_file_parser import ToolFileParser
  12. def get_attr(*, file: File, attr: FileAttribute):
  13. match attr:
  14. case FileAttribute.TYPE:
  15. return file.type.value
  16. case FileAttribute.SIZE:
  17. return file.size
  18. case FileAttribute.NAME:
  19. return file.filename
  20. case FileAttribute.MIME_TYPE:
  21. return file.mime_type
  22. case FileAttribute.TRANSFER_METHOD:
  23. return file.transfer_method.value
  24. case FileAttribute.URL:
  25. return file.remote_url
  26. case FileAttribute.EXTENSION:
  27. return file.extension
  28. case _:
  29. raise ValueError(f"Invalid file attribute: {attr}")
  30. def to_prompt_message_content(f: File, /):
  31. """
  32. Convert a File object to an ImagePromptMessageContent object.
  33. This function takes a File object and converts it to an ImagePromptMessageContent
  34. object, which can be used as a prompt for image-based AI models.
  35. Args:
  36. file (File): The File object to convert. Must be of type FileType.IMAGE.
  37. Returns:
  38. ImagePromptMessageContent: An object containing the image data and detail level.
  39. Raises:
  40. ValueError: If the file is not an image or if the file data is missing.
  41. Note:
  42. The detail level of the image prompt is determined by the file's extra_config.
  43. If not specified, it defaults to ImagePromptMessageContent.DETAIL.LOW.
  44. """
  45. match f.type:
  46. case FileType.IMAGE:
  47. if dify_config.MULTIMODAL_SEND_IMAGE_FORMAT == "url":
  48. data = _to_url(f)
  49. else:
  50. data = _to_base64_data_string(f)
  51. if f._extra_config and f._extra_config.image_config and f._extra_config.image_config.detail:
  52. detail = f._extra_config.image_config.detail
  53. else:
  54. detail = ImagePromptMessageContent.DETAIL.LOW
  55. return ImagePromptMessageContent(data=data, detail=detail)
  56. case FileType.AUDIO:
  57. encoded_string = _file_to_encoded_string(f)
  58. if f.extension is None:
  59. raise ValueError("Missing file extension")
  60. return AudioPromptMessageContent(data=encoded_string, format=f.extension.lstrip("."))
  61. case FileType.VIDEO:
  62. if dify_config.MULTIMODAL_SEND_VIDEO_FORMAT == "url":
  63. data = _to_url(f)
  64. else:
  65. data = _to_base64_data_string(f)
  66. return VideoPromptMessageContent(data=data, format=f.extension.lstrip("."))
  67. case _:
  68. raise ValueError(f"file type {f.type} is not supported")
  69. def download(f: File, /):
  70. if f.transfer_method == FileTransferMethod.TOOL_FILE:
  71. tool_file = file_repository.get_tool_file(session=db.session(), file=f)
  72. return _download_file_content(tool_file.file_key)
  73. elif f.transfer_method == FileTransferMethod.LOCAL_FILE:
  74. upload_file = file_repository.get_upload_file(session=db.session(), file=f)
  75. return _download_file_content(upload_file.key)
  76. # remote file
  77. response = ssrf_proxy.get(f.remote_url, follow_redirects=True)
  78. response.raise_for_status()
  79. return response.content
  80. def _download_file_content(path: str, /):
  81. """
  82. Download and return the contents of a file as bytes.
  83. This function loads the file from storage and ensures it's in bytes format.
  84. Args:
  85. path (str): The path to the file in storage.
  86. Returns:
  87. bytes: The contents of the file as a bytes object.
  88. Raises:
  89. ValueError: If the loaded file is not a bytes object.
  90. """
  91. data = storage.load(path, stream=False)
  92. if not isinstance(data, bytes):
  93. raise ValueError(f"file {path} is not a bytes object")
  94. return data
  95. def _get_encoded_string(f: File, /):
  96. match f.transfer_method:
  97. case FileTransferMethod.REMOTE_URL:
  98. response = ssrf_proxy.get(f.remote_url, follow_redirects=True)
  99. response.raise_for_status()
  100. content = response.content
  101. encoded_string = base64.b64encode(content).decode("utf-8")
  102. return encoded_string
  103. case FileTransferMethod.LOCAL_FILE:
  104. upload_file = file_repository.get_upload_file(session=db.session(), file=f)
  105. data = _download_file_content(upload_file.key)
  106. encoded_string = base64.b64encode(data).decode("utf-8")
  107. return encoded_string
  108. case FileTransferMethod.TOOL_FILE:
  109. tool_file = file_repository.get_tool_file(session=db.session(), file=f)
  110. data = _download_file_content(tool_file.file_key)
  111. encoded_string = base64.b64encode(data).decode("utf-8")
  112. return encoded_string
  113. case _:
  114. raise ValueError(f"Unsupported transfer method: {f.transfer_method}")
  115. def _to_base64_data_string(f: File, /):
  116. encoded_string = _get_encoded_string(f)
  117. return f"data:{f.mime_type};base64,{encoded_string}"
  118. def _file_to_encoded_string(f: File, /):
  119. match f.type:
  120. case FileType.IMAGE:
  121. return _to_base64_data_string(f)
  122. case FileType.VIDEO:
  123. return _to_base64_data_string(f)
  124. case FileType.AUDIO:
  125. return _get_encoded_string(f)
  126. case _:
  127. raise ValueError(f"file type {f.type} is not supported")
  128. def _to_url(f: File, /):
  129. if f.transfer_method == FileTransferMethod.REMOTE_URL:
  130. if f.remote_url is None:
  131. raise ValueError("Missing file remote_url")
  132. return f.remote_url
  133. elif f.transfer_method == FileTransferMethod.LOCAL_FILE:
  134. if f.related_id is None:
  135. raise ValueError("Missing file related_id")
  136. return helpers.get_signed_file_url(upload_file_id=f.related_id)
  137. elif f.transfer_method == FileTransferMethod.TOOL_FILE:
  138. # add sign url
  139. if f.related_id is None or f.extension is None:
  140. raise ValueError("Missing file related_id or extension")
  141. return ToolFileParser.get_tool_file_manager().sign_file(tool_file_id=f.related_id, extension=f.extension)
  142. else:
  143. raise ValueError(f"Unsupported transfer method: {f.transfer_method}")