| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- from typing import List, Optional, Any, Union, Dict
- from pydantic import BaseModel, ConfigDict, field_validator
- import re
- from datetime import datetime
- import json
- # --- User Schemas ---
- class UserBase(BaseModel):
- username: str
- email: Optional[str] = None
- role: Optional[str] = "user"
- class UserCreate(UserBase):
- password: str
- @field_validator("email")
- @classmethod
- def validate_email(cls, value: Optional[str]) -> Optional[str]:
- if value is None:
- return None
- normalized = value.strip().lower()
- if not re.fullmatch(r"[^\s@]+@[^\s@]+\.[^\s@]+", normalized):
- raise ValueError("请输入有效的邮箱地址")
- return normalized
- class UserResponse(UserBase):
- id: int
- created_at: datetime
-
- model_config = ConfigDict(from_attributes=True)
- class Token(BaseModel):
- access_token: str
- token_type: str
- class TokenData(BaseModel):
- username: Optional[str] = None
- # --- Persona Schemas ---
- class PersonaBase(BaseModel):
- name: str
- title: Optional[str] = None
- bio: Optional[str] = None
- theories: Optional[List[str]] = []
- stance: Optional[str] = None
- system_prompt: Optional[str] = None
- is_public: bool = False
- @field_validator('name')
- @classmethod
- def validate_name(cls, value: str) -> str:
- value = value.strip()
- if not value:
- raise ValueError('Persona name must not be blank')
- return value
- class PersonaCreate(PersonaBase):
- pass
- class PersonaUpdate(BaseModel):
- name: Optional[str] = None
- title: Optional[str] = None
- bio: Optional[str] = None
- theories: Optional[List[str]] = None
- stance: Optional[str] = None
- system_prompt: Optional[str] = None
- is_public: Optional[bool] = None
- @field_validator('name')
- @classmethod
- def validate_name(cls, value: Optional[str]) -> Optional[str]:
- if value is None:
- return value
- value = value.strip()
- if not value:
- raise ValueError('Persona name must not be blank')
- return value
- class PersonaResponse(PersonaBase):
- id: int
- owner_id: int
- created_at: datetime
- theories: Optional[Union[List[str], str]] = []
- model_config = ConfigDict(from_attributes=True)
- @field_validator('theories', mode='before')
- @classmethod
- def parse_theories(cls, v: Any) -> List[str]:
- if isinstance(v, str):
- try:
- parsed = json.loads(v)
- if isinstance(parsed, list):
- return parsed
- return []
- except json.JSONDecodeError:
- return []
- elif v is None:
- return []
- return v
- # --- Moderator Schemas ---
- class ModeratorBase(BaseModel):
- name: str
- title: Optional[str] = "主持人"
- bio: Optional[str] = None
- system_prompt: Optional[str] = None
- greeting_template: Optional[str] = None
- closing_template: Optional[str] = None
- summary_template: Optional[str] = None
- class ModeratorCreate(ModeratorBase):
- pass
- class ModeratorUpdate(ModeratorBase):
- pass
- class ModeratorResponse(ModeratorBase):
- id: int
- creator_id: int
- created_at: datetime
- model_config = ConfigDict(from_attributes=True)
- from .system_log import SystemLogCreate, SystemLogResponse
- # --- Forum Schemas ---
- class ForumBase(BaseModel):
- topic: str
- @field_validator('topic')
- @classmethod
- def validate_topic(cls, value: str) -> str:
- value = value.strip()
- if not value:
- raise ValueError('讨论主题不能为空')
- if len(value) > 200:
- raise ValueError('讨论主题不能超过 200 个字符')
- return value
- class ForumCreate(ForumBase):
- participant_ids: List[int]
- moderator_id: Optional[int] = None # Optional for backward compatibility (can use default)
- duration_minutes: int = 30
- @field_validator('participant_ids')
- @classmethod
- def validate_participants(cls, value: List[int]) -> List[int]:
- unique_ids = list(dict.fromkeys(value))
- if not unique_ids:
- raise ValueError('请至少选择一位智能体')
- if len(unique_ids) > 5:
- raise ValueError('每个论坛最多选择 5 位智能体')
- if any(persona_id <= 0 for persona_id in unique_ids):
- raise ValueError('智能体编号无效')
- return unique_ids
- @field_validator('duration_minutes')
- @classmethod
- def validate_duration(cls, value: int) -> int:
- if value < 1 or value > 120:
- raise ValueError('论坛时长必须在 1 到 120 分钟之间')
- return value
- class ForumParticipantResponse(BaseModel):
- persona_id: int
- thoughts_history: Optional[Union[List[Any], str]] = [] # Changed from List[str] to List[Any] to support dicts
- persona: Optional[PersonaResponse] = None
- model_config = ConfigDict(from_attributes=True)
- @field_validator('thoughts_history', mode='before')
- @classmethod
- def parse_thoughts_history(cls, v: Any) -> List[Any]:
- if isinstance(v, str):
- try:
- parsed = json.loads(v)
- if isinstance(parsed, list):
- return parsed
- # If it's a dict (single thought), wrap in list? Or return empty?
- # Based on log, it seems to be a list of dicts.
- return []
- except json.JSONDecodeError:
- return []
- elif isinstance(v, list):
- return v
- elif v is None:
- return []
- return [v] if v else []
- class ForumResponse(ForumBase):
- id: int
- creator_id: int
- moderator_id: Optional[int] = None
- status: str
- start_time: Optional[datetime] = None
- end_time: Optional[datetime] = None
- duration_minutes: Optional[int] = 30
- summary_history: Optional[Union[List[Any], str]] = [] # Changed to List[Any] for flexibility
- ablation_flags: Optional[Dict[str, bool]] = {}
- participants: Optional[List[ForumParticipantResponse]] = []
- moderator: Optional[ModeratorResponse] = None # Include moderator info
- model_config = ConfigDict(from_attributes=True)
- @field_validator('summary_history', mode='before')
- @classmethod
- def parse_summary_history(cls, v: Any) -> List[Any]:
- if isinstance(v, str):
- try:
- parsed = json.loads(v)
- if isinstance(parsed, list):
- return parsed
- return []
- except json.JSONDecodeError:
- return []
- elif isinstance(v, list):
- return v
- elif v is None:
- return []
- return [v] if v else []
- @field_validator('ablation_flags', mode='before')
- @classmethod
- def parse_ablation_flags(cls, v: Any) -> Dict[str, bool]:
- if isinstance(v, str):
- try:
- parsed = json.loads(v)
- return parsed if isinstance(parsed, dict) else {}
- except json.JSONDecodeError:
- return {}
- return v if isinstance(v, dict) else {}
- # --- Message Schemas ---
- class MessageBase(BaseModel):
- speaker_name: str
- content: str
- thought: Optional[str] = None # Added thought field
- turn_count: int = 0
- class MessageCreate(MessageBase):
- forum_id: int
- persona_id: Optional[int] = None
- moderator_id: Optional[int] = None
- class MessageResponse(MessageBase):
- id: int
- forum_id: int
- persona_id: Optional[int]
- moderator_id: Optional[int] = None
- timestamp: datetime
- thought: Optional[str] = None # Ensure it's in response
- model_config = ConfigDict(from_attributes=True)
- class TriggerAgentRequest(BaseModel):
- persona_id: Optional[int] = None
- class TriggerModeratorRequest(BaseModel):
- action: str = "auto" # auto, opening, summary, closing
- class GodGenerateRequest(BaseModel):
- prompt: str
- n: int = 1
- class ForumStartRequest(BaseModel):
- ablation_flags: Optional[Dict[str, bool]] = None
|