message_entities.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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. @property
  67. def data(self):
  68. return self.url or f"data:{self.mime_type};base64,{self.base64_data}"
  69. class VideoPromptMessageContent(MultiModalPromptMessageContent):
  70. type: Literal[PromptMessageContentType.VIDEO] = PromptMessageContentType.VIDEO
  71. class AudioPromptMessageContent(MultiModalPromptMessageContent):
  72. type: Literal[PromptMessageContentType.AUDIO] = PromptMessageContentType.AUDIO
  73. class ImagePromptMessageContent(MultiModalPromptMessageContent):
  74. """
  75. Model class for image prompt message content.
  76. """
  77. class DETAIL(StrEnum):
  78. LOW = "low"
  79. HIGH = "high"
  80. type: Literal[PromptMessageContentType.IMAGE] = PromptMessageContentType.IMAGE
  81. detail: DETAIL = DETAIL.LOW
  82. class DocumentPromptMessageContent(MultiModalPromptMessageContent):
  83. type: Literal[PromptMessageContentType.DOCUMENT] = PromptMessageContentType.DOCUMENT
  84. PromptMessageContentUnionTypes = Annotated[
  85. Union[
  86. TextPromptMessageContent,
  87. ImagePromptMessageContent,
  88. DocumentPromptMessageContent,
  89. AudioPromptMessageContent,
  90. VideoPromptMessageContent,
  91. ],
  92. Field(discriminator="type"),
  93. ]
  94. CONTENT_TYPE_MAPPING: Mapping[PromptMessageContentType, type[PromptMessageContent]] = {
  95. PromptMessageContentType.TEXT: TextPromptMessageContent,
  96. PromptMessageContentType.IMAGE: ImagePromptMessageContent,
  97. PromptMessageContentType.AUDIO: AudioPromptMessageContent,
  98. PromptMessageContentType.VIDEO: VideoPromptMessageContent,
  99. PromptMessageContentType.DOCUMENT: DocumentPromptMessageContent,
  100. }
  101. class PromptMessage(ABC, BaseModel):
  102. """
  103. Model class for prompt message.
  104. """
  105. role: PromptMessageRole
  106. content: Optional[str | list[PromptMessageContentUnionTypes]] = None
  107. name: Optional[str] = None
  108. def is_empty(self) -> bool:
  109. """
  110. Check if prompt message is empty.
  111. :return: True if prompt message is empty, False otherwise
  112. """
  113. return not self.content
  114. def get_text_content(self) -> str:
  115. """
  116. Get text content from prompt message.
  117. :return: Text content as string, empty string if no text content
  118. """
  119. if isinstance(self.content, str):
  120. return self.content
  121. elif isinstance(self.content, list):
  122. text_parts = []
  123. for item in self.content:
  124. if isinstance(item, TextPromptMessageContent):
  125. text_parts.append(item.data)
  126. return "".join(text_parts)
  127. else:
  128. return ""
  129. @field_validator("content", mode="before")
  130. @classmethod
  131. def validate_content(cls, v):
  132. if isinstance(v, list):
  133. prompts = []
  134. for prompt in v:
  135. if isinstance(prompt, PromptMessageContent):
  136. if not isinstance(prompt, TextPromptMessageContent | MultiModalPromptMessageContent):
  137. prompt = CONTENT_TYPE_MAPPING[prompt.type].model_validate(prompt.model_dump())
  138. elif isinstance(prompt, dict):
  139. prompt = CONTENT_TYPE_MAPPING[prompt["type"]].model_validate(prompt)
  140. else:
  141. raise ValueError(f"invalid prompt message {prompt}")
  142. prompts.append(prompt)
  143. return prompts
  144. return v
  145. @field_serializer("content")
  146. def serialize_content(
  147. self, content: Optional[Union[str, Sequence[PromptMessageContent]]]
  148. ) -> Optional[str | list[dict[str, Any] | PromptMessageContent] | Sequence[PromptMessageContent]]:
  149. if content is None or isinstance(content, str):
  150. return content
  151. if isinstance(content, list):
  152. return [item.model_dump() if hasattr(item, "model_dump") else item for item in content]
  153. return content
  154. class UserPromptMessage(PromptMessage):
  155. """
  156. Model class for user prompt message.
  157. """
  158. role: PromptMessageRole = PromptMessageRole.USER
  159. class AssistantPromptMessage(PromptMessage):
  160. """
  161. Model class for assistant prompt message.
  162. """
  163. class ToolCall(BaseModel):
  164. """
  165. Model class for assistant prompt message tool call.
  166. """
  167. class ToolCallFunction(BaseModel):
  168. """
  169. Model class for assistant prompt message tool call function.
  170. """
  171. name: str
  172. arguments: str
  173. id: str
  174. type: str
  175. function: ToolCallFunction
  176. @field_validator("id", mode="before")
  177. @classmethod
  178. def transform_id_to_str(cls, value) -> str:
  179. if not isinstance(value, str):
  180. return str(value)
  181. else:
  182. return value
  183. role: PromptMessageRole = PromptMessageRole.ASSISTANT
  184. tool_calls: list[ToolCall] = []
  185. def is_empty(self) -> bool:
  186. """
  187. Check if prompt message is empty.
  188. :return: True if prompt message is empty, False otherwise
  189. """
  190. if not super().is_empty() and not self.tool_calls:
  191. return False
  192. return True
  193. class SystemPromptMessage(PromptMessage):
  194. """
  195. Model class for system prompt message.
  196. """
  197. role: PromptMessageRole = PromptMessageRole.SYSTEM
  198. class ToolPromptMessage(PromptMessage):
  199. """
  200. Model class for tool prompt message.
  201. """
  202. role: PromptMessageRole = PromptMessageRole.TOOL
  203. tool_call_id: str
  204. def is_empty(self) -> bool:
  205. """
  206. Check if prompt message is empty.
  207. :return: True if prompt message is empty, False otherwise
  208. """
  209. if not super().is_empty() and not self.tool_call_id:
  210. return False
  211. return True