Files
012-kaopeilian/backend/app/schemas/practice.py
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

319 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
陪练功能相关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