message_entities.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. from abc import ABC
  2. from collections.abc import Mapping, Sequence
  3. from enum import Enum, StrEnum
  4. from typing import Annotated, Any, Literal, Optional, Union
  5. from pydantic import BaseModel, Field, field_serializer, field_validator
  6. class PromptMessageRole(Enum):
  7. """
  8. Enum class for prompt message.
  9. """
  10. SYSTEM = "system"
  11. USER = "user"
  12. ASSISTANT = "assistant"
  13. TOOL = "tool"
  14. @classmethod
  15. def value_of(cls, value: str) -> "PromptMessageRole":
  16. """
  17. Get value of given mode.
  18. :param value: mode value
  19. :return: mode
  20. """
  21. for mode in cls:
  22. if mode.value == value:
  23. return mode
  24. raise ValueError(f"invalid prompt message type value {value}")
  25. class PromptMessageTool(BaseModel):
  26. """
  27. Model class for prompt message tool.
  28. """
  29. name: str
  30. description: str
  31. parameters: dict
  32. class PromptMessageFunction(BaseModel):
  33. """
  34. Model class for prompt message function.
  35. """
  36. type: str = "function"
  37. function: PromptMessageTool
  38. class PromptMessageContentType(StrEnum):
  39. """
  40. Enum class for prompt message content type.
  41. """
  42. TEXT = "text"
  43. IMAGE = "image"
  44. AUDIO = "audio"
  45. VIDEO = "video"
  46. DOCUMENT = "document"
  47. class PromptMessageContent(ABC, BaseModel):
  48. """
  49. Model class for prompt message content.
  50. """
  51. type: PromptMessageContentType
  52. class TextPromptMessageContent(PromptMessageContent):
  53. """
  54. Model class for text prompt message content.
  55. """
  56. type: Literal[PromptMessageContentType.TEXT] = PromptMessageContentType.TEXT
  57. data: str
  58. class MultiModalPromptMessageContent(PromptMessageContent):
  59. """
  60. Model class for multi-modal prompt message content.
  61. """
  62. format: str = Field(default=..., description="the format of multi-modal file")
  63. base64_data: str = Field(default="", description="the base64 data of multi-modal file")
  64. url: str = Field(default="", description="the url of multi-modal file")
  65. mime_type: str = Field(default=..., description="the mime type of multi-modal file")
  66. filename: str = Field(default="", description="the filename of multi-modal file")
  67. @property
  68. def data(self):
  69. return self.url or f"data:{self.mime_type};base64,{self.base64_data}"
  70. class VideoPromptMessageContent(MultiModalPromptMessageContent):
  71. type: Literal[PromptMessageContentType.VIDEO] = PromptMessageContentType.VIDEO
  72. class AudioPromptMessageContent(MultiModalPromptMessageContent):
  73. type: Literal[PromptMessageContentType.AUDIO] = PromptMessageContentType.AUDIO
  74. class ImagePromptMessageContent(MultiModalPromptMessageContent):
  75. """
  76. Model class for image prompt message content.
  77. """
  78. class DETAIL(StrEnum):
  79. LOW = "low"
  80. HIGH = "high"
  81. type: Literal[PromptMessageContentType.IMAGE] = PromptMessageContentType.IMAGE
  82. detail: DETAIL = DETAIL.LOW
  83. class DocumentPromptMessageContent(MultiModalPromptMessageContent):
  84. type: Literal[PromptMessageContentType.DOCUMENT] = PromptMessageContentType.DOCUMENT
  85. PromptMessageContentUnionTypes = Annotated[
  86. Union[
  87. TextPromptMessageContent,
  88. ImagePromptMessageContent,
  89. DocumentPromptMessageContent,
  90. AudioPromptMessageContent,
  91. VideoPromptMessageContent,
  92. ],
  93. Field(discriminator="type"),
  94. ]
  95. CONTENT_TYPE_MAPPING: Mapping[PromptMessageContentType, type[PromptMessageContent]] = {
  96. PromptMessageContentType.TEXT: TextPromptMessageContent,
  97. PromptMessageContentType.IMAGE: ImagePromptMessageContent,
  98. PromptMessageContentType.AUDIO: AudioPromptMessageContent,
  99. PromptMessageContentType.VIDEO: VideoPromptMessageContent,
  100. PromptMessageContentType.DOCUMENT: DocumentPromptMessageContent,
  101. }
  102. class PromptMessage(ABC, BaseModel):
  103. """
  104. Model class for prompt message.
  105. """
  106. role: PromptMessageRole
  107. content: Optional[str | list[PromptMessageContentUnionTypes]] = None
  108. name: Optional[str] = None
  109. def is_empty(self) -> bool:
  110. """
  111. Check if prompt message is empty.
  112. :return: True if prompt message is empty, False otherwise
  113. """
  114. return not self.content
  115. def get_text_content(self) -> str:
  116. """
  117. Get text content from prompt message.
  118. :return: Text content as string, empty string if no text content
  119. """
  120. if isinstance(self.content, str):
  121. return self.content
  122. elif isinstance(self.content, list):
  123. text_parts = []
  124. for item in self.content:
  125. if isinstance(item, TextPromptMessageContent):
  126. text_parts.append(item.data)
  127. return "".join(text_parts)
  128. else:
  129. return ""
  130. @field_validator("content", mode="before")
  131. @classmethod
  132. def validate_content(cls, v):
  133. if isinstance(v, list):
  134. prompts = []
  135. for prompt in v:
  136. if isinstance(prompt, PromptMessageContent):
  137. if not isinstance(prompt, TextPromptMessageContent | MultiModalPromptMessageContent):
  138. prompt = CONTENT_TYPE_MAPPING[prompt.type].model_validate(prompt.model_dump())
  139. elif isinstance(prompt, dict):
  140. prompt = CONTENT_TYPE_MAPPING[prompt["type"]].model_validate(prompt)
  141. else:
  142. raise ValueError(f"invalid prompt message {prompt}")
  143. prompts.append(prompt)
  144. return prompts
  145. return v
  146. @field_serializer("content")
  147. def serialize_content(
  148. self, content: Optional[Union[str, Sequence[PromptMessageContent]]]
  149. ) -> Optional[str | list[dict[str, Any] | PromptMessageContent] | Sequence[PromptMessageContent]]:
  150. if content is None or isinstance(content, str):
  151. return content
  152. if isinstance(content, list):
  153. return [item.model_dump() if hasattr(item, "model_dump") else item for item in content]
  154. return content
  155. class UserPromptMessage(PromptMessage):
  156. """
  157. Model class for user prompt message.
  158. """
  159. role: PromptMessageRole = PromptMessageRole.USER
  160. class AssistantPromptMessage(PromptMessage):
  161. """
  162. Model class for assistant prompt message.
  163. """
  164. class ToolCall(BaseModel):
  165. """
  166. Model class for assistant prompt message tool call.
  167. """
  168. class ToolCallFunction(BaseModel):
  169. """
  170. Model class for assistant prompt message tool call function.
  171. """
  172. name: str
  173. arguments: str
  174. id: str
  175. type: str
  176. function: ToolCallFunction
  177. @field_validator("id", mode="before")
  178. @classmethod
  179. def transform_id_to_str(cls, value) -> str:
  180. if not isinstance(value, str):
  181. return str(value)
  182. else:
  183. return value
  184. role: PromptMessageRole = PromptMessageRole.ASSISTANT
  185. tool_calls: list[ToolCall] = []
  186. def is_empty(self) -> bool:
  187. """
  188. Check if prompt message is empty.
  189. :return: True if prompt message is empty, False otherwise
  190. """
  191. if not super().is_empty() and not self.tool_calls:
  192. return False
  193. return True
  194. class SystemPromptMessage(PromptMessage):
  195. """
  196. Model class for system prompt message.
  197. """
  198. role: PromptMessageRole = PromptMessageRole.SYSTEM
  199. class ToolPromptMessage(PromptMessage):
  200. """
  201. Model class for tool prompt message.
  202. """
  203. role: PromptMessageRole = PromptMessageRole.TOOL
  204. tool_call_id: str
  205. def is_empty(self) -> bool:
  206. """
  207. Check if prompt message is empty.
  208. :return: True if prompt message is empty, False otherwise
  209. """
  210. if not super().is_empty() and not self.tool_call_id:
  211. return False
  212. return True