models.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. from collections.abc import Mapping, Sequence
  2. from typing import Optional
  3. from pydantic import BaseModel, Field, model_validator
  4. from core.model_runtime.entities.message_entities import ImagePromptMessageContent
  5. from . import helpers
  6. from .constants import FILE_MODEL_IDENTITY
  7. from .enums import FileTransferMethod, FileType
  8. from .tool_file_parser import ToolFileParser
  9. class ImageConfig(BaseModel):
  10. """
  11. NOTE: This part of validation is deprecated, but still used in app features "Image Upload".
  12. """
  13. number_limits: int = 0
  14. transfer_methods: Sequence[FileTransferMethod] = Field(default_factory=list)
  15. detail: ImagePromptMessageContent.DETAIL | None = None
  16. class FileExtraConfig(BaseModel):
  17. """
  18. File Upload Entity.
  19. """
  20. image_config: Optional[ImageConfig] = None
  21. allowed_file_types: Sequence[FileType] = Field(default_factory=list)
  22. allowed_extensions: Sequence[str] = Field(default_factory=list)
  23. allowed_upload_methods: Sequence[FileTransferMethod] = Field(default_factory=list)
  24. number_limits: int = 0
  25. class File(BaseModel):
  26. dify_model_identity: str = FILE_MODEL_IDENTITY
  27. id: Optional[str] = None # message file id
  28. tenant_id: str
  29. type: FileType
  30. transfer_method: FileTransferMethod
  31. remote_url: Optional[str] = None # remote url
  32. related_id: Optional[str] = None
  33. filename: Optional[str] = None
  34. extension: Optional[str] = Field(default=None, description="File extension, should contains dot")
  35. mime_type: Optional[str] = None
  36. size: int = -1
  37. _extra_config: FileExtraConfig | None = None
  38. def to_dict(self) -> Mapping[str, str | int | None]:
  39. data = self.model_dump(mode="json")
  40. return {
  41. **data,
  42. "url": self.generate_url(),
  43. }
  44. @property
  45. def markdown(self) -> str:
  46. url = self.generate_url()
  47. if self.type == FileType.IMAGE:
  48. text = f'![{self.filename or ""}]({url})'
  49. else:
  50. text = f"[{self.filename or url}]({url})"
  51. return text
  52. def generate_url(self) -> Optional[str]:
  53. if self.type == FileType.IMAGE:
  54. if self.transfer_method == FileTransferMethod.REMOTE_URL:
  55. return self.remote_url
  56. elif self.transfer_method == FileTransferMethod.LOCAL_FILE:
  57. if self.related_id is None:
  58. raise ValueError("Missing file related_id")
  59. return helpers.get_signed_file_url(upload_file_id=self.related_id)
  60. elif self.transfer_method == FileTransferMethod.TOOL_FILE:
  61. assert self.related_id is not None
  62. assert self.extension is not None
  63. return ToolFileParser.get_tool_file_manager().sign_file(
  64. tool_file_id=self.related_id, extension=self.extension
  65. )
  66. else:
  67. if self.transfer_method == FileTransferMethod.REMOTE_URL:
  68. return self.remote_url
  69. elif self.transfer_method == FileTransferMethod.LOCAL_FILE:
  70. if self.related_id is None:
  71. raise ValueError("Missing file related_id")
  72. return helpers.get_signed_file_url(upload_file_id=self.related_id)
  73. elif self.transfer_method == FileTransferMethod.TOOL_FILE:
  74. assert self.related_id is not None
  75. assert self.extension is not None
  76. return ToolFileParser.get_tool_file_manager().sign_file(
  77. tool_file_id=self.related_id, extension=self.extension
  78. )
  79. @model_validator(mode="after")
  80. def validate_after(self):
  81. match self.transfer_method:
  82. case FileTransferMethod.REMOTE_URL:
  83. if not self.remote_url:
  84. raise ValueError("Missing file url")
  85. if not isinstance(self.remote_url, str) or not self.remote_url.startswith("http"):
  86. raise ValueError("Invalid file url")
  87. case FileTransferMethod.LOCAL_FILE:
  88. if not self.related_id:
  89. raise ValueError("Missing file related_id")
  90. case FileTransferMethod.TOOL_FILE:
  91. if not self.related_id:
  92. raise ValueError("Missing file related_id")
  93. # Validate the extra config.
  94. if not self._extra_config:
  95. return self
  96. if self._extra_config.allowed_file_types:
  97. if self.type not in self._extra_config.allowed_file_types and self.type != FileType.CUSTOM:
  98. raise ValueError(f"Invalid file type: {self.type}")
  99. if self._extra_config.allowed_extensions and self.extension not in self._extra_config.allowed_extensions:
  100. raise ValueError(f"Invalid file extension: {self.extension}")
  101. if (
  102. self._extra_config.allowed_upload_methods
  103. and self.transfer_method not in self._extra_config.allowed_upload_methods
  104. ):
  105. raise ValueError(f"Invalid transfer method: {self.transfer_method}")
  106. match self.type:
  107. case FileType.IMAGE:
  108. # NOTE: This part of validation is deprecated, but still used in app features "Image Upload".
  109. if not self._extra_config.image_config:
  110. return self
  111. # TODO: skip check if transfer_methods is empty, because many test cases are not setting this field
  112. if (
  113. self._extra_config.image_config.transfer_methods
  114. and self.transfer_method not in self._extra_config.image_config.transfer_methods
  115. ):
  116. raise ValueError(f"Invalid transfer method: {self.transfer_method}")
  117. return self