""" 陪练功能相关Schema定义 """ from typing import Optional, List from datetime import datetime from pydantic import BaseModel, Field, field_validator # ==================== 枚举类型 ==================== class SceneType: """场景类型枚举""" PHONE = "phone" # 电话销售 FACE = "face" # 面对面销售 COMPLAINT = "complaint" # 客户投诉 AFTER_SALES = "after-sales" # 售后服务 PRODUCT_INTRO = "product-intro" # 产品介绍 class Difficulty: """难度等级枚举""" BEGINNER = "beginner" # 入门 JUNIOR = "junior" # 初级 INTERMEDIATE = "intermediate" # 中级 SENIOR = "senior" # 高级 EXPERT = "expert" # 专家 class SceneStatus: """场景状态枚举""" ACTIVE = "active" # 启用 INACTIVE = "inactive" # 禁用 # ==================== 场景Schema ==================== class PracticeSceneBase(BaseModel): """陪练场景基础Schema""" name: str = Field(..., max_length=200, description="场景名称") description: Optional[str] = Field(None, description="场景描述") type: str = Field(..., description="场景类型: phone/face/complaint/after-sales/product-intro") difficulty: str = Field(..., description="难度等级: beginner/junior/intermediate/senior/expert") status: str = Field(default="active", description="状态: active/inactive") background: str = Field(..., description="场景背景设定") ai_role: str = Field(..., description="AI角色描述") objectives: List[str] = Field(..., description="练习目标数组") keywords: Optional[List[str]] = Field(default=None, description="关键词数组") duration: int = Field(default=10, ge=1, le=120, description="预计时长(分钟)") @field_validator('type') @classmethod def validate_type(cls, v): """验证场景类型""" valid_types = ['phone', 'face', 'complaint', 'after-sales', 'product-intro'] if v not in valid_types: raise ValueError(f"场景类型必须是: {', '.join(valid_types)}") return v @field_validator('difficulty') @classmethod def validate_difficulty(cls, v): """验证难度等级""" valid_difficulties = ['beginner', 'junior', 'intermediate', 'senior', 'expert'] if v not in valid_difficulties: raise ValueError(f"难度等级必须是: {', '.join(valid_difficulties)}") return v @field_validator('status') @classmethod def validate_status(cls, v): """验证状态""" valid_statuses = ['active', 'inactive'] if v not in valid_statuses: raise ValueError(f"状态必须是: {', '.join(valid_statuses)}") return v @field_validator('objectives') @classmethod def validate_objectives(cls, v): """验证练习目标""" if not v or len(v) < 1: raise ValueError("至少需要1个练习目标") if len(v) > 10: raise ValueError("练习目标不能超过10个") return v class PracticeSceneCreate(PracticeSceneBase): """创建陪练场景Schema""" pass class PracticeSceneUpdate(BaseModel): """更新陪练场景Schema(所有字段可选)""" name: Optional[str] = Field(None, max_length=200, description="场景名称") description: Optional[str] = Field(None, description="场景描述") type: Optional[str] = Field(None, description="场景类型") difficulty: Optional[str] = Field(None, description="难度等级") status: Optional[str] = Field(None, description="状态") background: Optional[str] = Field(None, description="场景背景设定") ai_role: Optional[str] = Field(None, description="AI角色描述") objectives: Optional[List[str]] = Field(None, description="练习目标数组") keywords: Optional[List[str]] = Field(None, description="关键词数组") duration: Optional[int] = Field(None, ge=1, le=120, description="预计时长(分钟)") class PracticeSceneResponse(PracticeSceneBase): """陪练场景响应Schema""" id: int usage_count: int rating: float created_by: Optional[int] = None updated_by: Optional[int] = None created_at: datetime updated_at: datetime class Config: from_attributes = True # ==================== 对话Schema ==================== class StartPracticeRequest(BaseModel): """开始陪练对话请求Schema""" # 场景信息(首次消息必填,后续消息可选) scene_id: Optional[int] = Field(None, description="场景ID(可选)") scene_name: Optional[str] = Field(None, description="场景名称") scene_description: Optional[str] = Field(None, description="场景描述") scene_background: Optional[str] = Field(None, description="场景背景") scene_ai_role: Optional[str] = Field(None, description="AI角色") scene_objectives: Optional[List[str]] = Field(None, description="练习目标") scene_keywords: Optional[List[str]] = Field(None, description="关键词") # 对话信息 user_message: str = Field(..., description="用户消息") conversation_id: Optional[str] = Field(None, description="对话ID(续接对话时必填)") is_first: bool = Field(..., description="是否首次消息") @field_validator('scene_name') @classmethod def validate_scene_name_for_first(cls, v, info): """首次消息时场景名称必填""" if info.data.get('is_first') and not v: raise ValueError("首次消息时场景名称必填") return v @field_validator('scene_background') @classmethod def validate_scene_background_for_first(cls, v, info): """首次消息时场景背景必填""" if info.data.get('is_first') and not v: raise ValueError("首次消息时场景背景必填") return v @field_validator('scene_ai_role') @classmethod def validate_scene_ai_role_for_first(cls, v, info): """首次消息时AI角色必填""" if info.data.get('is_first') and not v: raise ValueError("首次消息时AI角色必填") return v @field_validator('scene_objectives') @classmethod def validate_scene_objectives_for_first(cls, v, info): """首次消息时练习目标必填""" if info.data.get('is_first') and (not v or len(v) == 0): raise ValueError("首次消息时练习目标必填") return v class InterruptPracticeRequest(BaseModel): """中断对话请求Schema""" conversation_id: str = Field(..., description="对话ID") chat_id: str = Field(..., description="聊天ID") class ConversationInfo(BaseModel): """对话信息Schema""" id: str = Field(..., description="对话ID") name: str = Field(..., description="对话名称") created_at: int = Field(..., description="创建时间(时间戳)") class ConversationsResponse(BaseModel): """对话列表响应Schema""" items: List[ConversationInfo] has_more: bool page: int size: int # ==================== 场景提取Schema ==================== class ExtractSceneRequest(BaseModel): """提取场景请求Schema""" course_id: int = Field(..., description="课程ID") class ExtractedSceneData(BaseModel): """提取的场景数据Schema""" name: str = Field(..., description="场景名称") description: str = Field(..., description="场景描述") type: str = Field(..., description="场景类型") difficulty: str = Field(..., description="难度等级") background: str = Field(..., description="场景背景") ai_role: str = Field(..., description="AI角色描述") objectives: List[str] = Field(..., description="练习目标数组") keywords: Optional[List[str]] = Field(default=[], description="关键词数组") class ExtractSceneResponse(BaseModel): """提取场景响应Schema""" scene: ExtractedSceneData = Field(..., description="场景数据") workflow_run_id: str = Field(..., description="工作流运行ID") task_id: str = Field(..., description="任务ID") # ==================== 陪练会话Schema ==================== class PracticeSessionCreate(BaseModel): """创建陪练会话请求Schema""" scene_id: Optional[int] = Field(None, description="场景ID") scene_name: str = Field(..., description="场景名称") scene_type: Optional[str] = Field(None, description="场景类型") conversation_id: Optional[str] = Field(None, description="Coze对话ID") class PracticeSessionResponse(BaseModel): """陪练会话响应Schema""" id: int session_id: str user_id: int scene_id: Optional[int] scene_name: str scene_type: Optional[str] conversation_id: Optional[str] start_time: datetime end_time: Optional[datetime] duration_seconds: int turns: int status: str created_at: datetime class Config: from_attributes = True class SaveDialogueRequest(BaseModel): """保存对话记录请求Schema""" session_id: str = Field(..., description="会话ID") speaker: str = Field(..., description="说话人: user/ai") content: str = Field(..., description="对话内容") sequence: int = Field(..., ge=1, description="顺序号(从1开始)") class PracticeDialogueResponse(BaseModel): """对话记录响应Schema""" id: int session_id: str speaker: str content: str timestamp: datetime sequence: int class Config: from_attributes = True # ==================== 分析报告Schema ==================== class ScoreBreakdownItem(BaseModel): """分数细分项""" name: str score: int = Field(..., ge=0, le=100) description: str class AbilityDimensionItem(BaseModel): """能力维度项""" name: str score: int = Field(..., ge=0, le=100) feedback: str class DialogueReviewItem(BaseModel): """对话复盘项""" speaker: str time: str content: str tags: List[str] = Field(default_factory=list) comment: str = Field(default="") class SuggestionItem(BaseModel): """改进建议项""" title: str content: str example: Optional[str] = None class PracticeAnalysisResult(BaseModel): """陪练分析结果Schema""" total_score: int = Field(..., ge=0, le=100, description="综合得分") score_breakdown: List[ScoreBreakdownItem] = Field(..., description="分数细分") ability_dimensions: List[AbilityDimensionItem] = Field(..., description="能力维度") dialogue_review: List[DialogueReviewItem] = Field(..., description="对话复盘") suggestions: List[SuggestionItem] = Field(..., description="改进建议") class PracticeReportResponse(BaseModel): """陪练报告响应Schema""" session_info: PracticeSessionResponse analysis: PracticeAnalysisResult class Config: from_attributes = True