models.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """
  2. 数据模型定义
  3. """
  4. from typing import Literal, Optional, Dict
  5. from pydantic import BaseModel
  6. # 消息类型
  7. MessageType = Literal[
  8. "audio_chunk",
  9. "audio_end",
  10. "cancel",
  11. "text_result",
  12. "emotion",
  13. "audio_response",
  14. "error",
  15. "ping",
  16. "pong",
  17. "energy_stone_spawn",
  18. "energy_stone_event",
  19. "user_level",
  20. ]
  21. # 情感类型
  22. EmotionType = Literal[
  23. # 基础情感
  24. "neutral", # 中性/平静
  25. "happy", # 开心/愉悦
  26. "sad", # 难过/悲伤
  27. "excited", # 兴奋/激动
  28. "angry", # 生气/愤怒
  29. "surprised", # 惊讶/意外
  30. "thoughtful", # 深思/沉思
  31. "encouraging", # 鼓励/支持
  32. # 扩展情感
  33. "confused", # 困惑/疑惑
  34. "worried", # 担忧/焦虑
  35. "satisfied", # 满意/满足
  36. "friendly", # 友好/亲切
  37. "sorry", # 抱歉/歉意
  38. ]
  39. # 错误代码
  40. ErrorCode = Literal[
  41. "STT_ERROR",
  42. "LLM_ERROR",
  43. "TTS_ERROR",
  44. "NETWORK_ERROR",
  45. "RATE_LIMIT",
  46. "INVALID_AUDIO",
  47. "CANCELLED",
  48. "SESSION_INVALIDATED",
  49. ]
  50. class BaseMessage(BaseModel):
  51. """基础消息"""
  52. type: MessageType
  53. requestId: str
  54. timestamp: int
  55. token: Optional[str] = None # 会话token(客户端->服务端鉴权)
  56. class AudioChunkMessage(BaseMessage):
  57. """音频数据块消息"""
  58. type: Literal["audio_chunk"] = "audio_chunk"
  59. data: str
  60. sampleRate: int = 16000
  61. channels: int = 1
  62. bitDepth: int = 16
  63. chunkIndex: int
  64. # 情景对话:首条语音时携带当前场景/话题,供后端落库
  65. scene: Optional[str] = None
  66. topic: Optional[str] = None
  67. class AudioEndMessage(BaseMessage):
  68. """音频结束消息"""
  69. type: Literal["audio_end"] = "audio_end"
  70. totalChunks: int
  71. speechDuration: Optional[int] = None # 说话时长(毫秒)
  72. scene: Optional[str] = None
  73. topic: Optional[str] = None
  74. class CancelMessage(BaseMessage):
  75. """取消消息"""
  76. type: Literal["cancel"] = "cancel"
  77. class TextResultMessage(BaseMessage):
  78. """文本结果消息。source: user=用户STT识别结果(不用于字幕),assistant=AI回复(用于字幕拼接)"""
  79. type: Literal["text_result"] = "text_result"
  80. text: str
  81. isFinal: bool
  82. source: Optional[Literal["user", "assistant"]] = None # 不传则前端按原逻辑处理
  83. class EmotionMessage(BaseMessage):
  84. """情感消息"""
  85. type: Literal["emotion"] = "emotion"
  86. emotion: EmotionType
  87. intensity: Optional[float] = None
  88. class AudioResponseMessage(BaseMessage):
  89. """音频响应消息"""
  90. type: Literal["audio_response"] = "audio_response"
  91. data: str
  92. sampleRate: int = 24000 # 流式 TTS 使用 24kHz
  93. channels: int = 1
  94. bitDepth: int = 16
  95. format: Literal["pcm", "wav"] = "pcm" # 音频格式:pcm(流式)或 wav(完整)
  96. chunkIndex: int
  97. isLast: bool
  98. class ErrorMessage(BaseMessage):
  99. """错误消息"""
  100. type: Literal["error"] = "error"
  101. code: ErrorCode
  102. message: str
  103. class PingMessage(BaseMessage):
  104. """Ping消息"""
  105. type: Literal["ping"] = "ping"
  106. class PongMessage(BaseMessage):
  107. """Pong消息"""
  108. type: Literal["pong"] = "pong"
  109. StoneType = Literal["mission", "conversation"]
  110. EnergyStoneEvent = Literal["click", "timeout"]
  111. class EnergyStoneSpawnMessage(BaseMessage):
  112. """能量石生成消息"""
  113. type: Literal["energy_stone_spawn"] = "energy_stone_spawn"
  114. stoneId: str
  115. stoneType: StoneType
  116. ttlSeconds: int
  117. appearAt: int
  118. metadata: Optional[Dict] = None
  119. class EnergyStoneEventMessage(BaseMessage):
  120. """能量石交互事件"""
  121. type: Literal["energy_stone_event"] = "energy_stone_event"
  122. stoneId: str
  123. event: EnergyStoneEvent
  124. clientTs: Optional[int] = None
  125. position: Optional[Dict] = None
  126. class UserLevelMessage(BaseMessage):
  127. """用户等级更新消息"""
  128. type: Literal["user_level"] = "user_level"
  129. level: int
  130. totalDurationMs: int