Files
AI-VideoAssistant/api/app/schemas.py
2026-02-08 22:39:55 +08:00

535 lines
12 KiB
Python

from datetime import datetime
from enum import Enum
from typing import List, Optional
from pydantic import BaseModel
# ============ Enums ============
class AssistantConfigMode(str, Enum):
PLATFORM = "platform"
DIFY = "dify"
FASTGPT = "fastgpt"
NONE = "none"
class LLMModelType(str, Enum):
TEXT = "text"
EMBEDDING = "embedding"
RERANK = "rerank"
class ASRLanguage(str, Enum):
ZH = "zh"
EN = "en"
MULTILINGUAL = "Multi-lingual"
class VoiceGender(str, Enum):
MALE = "Male"
FEMALE = "Female"
class CallRecordSource(str, Enum):
DEBUG = "debug"
EXTERNAL = "external"
class CallRecordStatus(str, Enum):
CONNECTED = "connected"
MISSED = "missed"
FAILED = "failed"
# ============ Voice ============
class VoiceBase(BaseModel):
name: str
vendor: str
gender: str # "Male" | "Female"
language: str # "zh" | "en"
description: str = ""
class VoiceCreate(VoiceBase):
id: Optional[str] = None
model: Optional[str] = None # 厂商语音模型标识
voice_key: Optional[str] = None # 厂商voice_key
speed: float = 1.0
gain: int = 0
pitch: int = 0
enabled: bool = True
class VoiceUpdate(BaseModel):
name: Optional[str] = None
vendor: Optional[str] = None
gender: Optional[str] = None
language: Optional[str] = None
description: Optional[str] = None
model: Optional[str] = None
voice_key: Optional[str] = None
speed: Optional[float] = None
gain: Optional[int] = None
pitch: Optional[int] = None
enabled: Optional[bool] = None
class VoiceOut(VoiceBase):
id: str
user_id: Optional[int] = None
model: Optional[str] = None
voice_key: Optional[str] = None
speed: float = 1.0
gain: int = 0
pitch: int = 0
enabled: bool = True
is_system: bool = False
created_at: Optional[datetime] = None
class Config:
from_attributes = True
class VoicePreviewRequest(BaseModel):
text: str
api_key: Optional[str] = None
speed: Optional[float] = None
gain: Optional[int] = None
pitch: Optional[int] = None
class VoicePreviewResponse(BaseModel):
success: bool
audio_url: Optional[str] = None
duration_ms: Optional[int] = None
error: Optional[str] = None
class VendorCredentialUpsert(BaseModel):
vendor_name: Optional[str] = None
api_key: str
base_url: Optional[str] = None
class VendorCredentialOut(BaseModel):
vendor_key: str
vendor_name: str
api_key: str
base_url: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
# ============ LLM Model ============
class LLMModelBase(BaseModel):
name: str
vendor: str
type: LLMModelType
base_url: str
api_key: str
model_name: Optional[str] = None
temperature: Optional[float] = None
context_length: Optional[int] = None
enabled: bool = True
class LLMModelCreate(LLMModelBase):
id: Optional[str] = None
class LLMModelUpdate(BaseModel):
name: Optional[str] = None
base_url: Optional[str] = None
api_key: Optional[str] = None
model_name: Optional[str] = None
temperature: Optional[float] = None
context_length: Optional[int] = None
enabled: Optional[bool] = None
class LLMModelOut(LLMModelBase):
id: str
user_id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class LLMModelTestResponse(BaseModel):
success: bool
latency_ms: Optional[int] = None
message: Optional[str] = None
# ============ ASR Model ============
class ASRModelBase(BaseModel):
name: str
vendor: str
language: str # "zh" | "en" | "Multi-lingual"
base_url: str
api_key: str
model_name: Optional[str] = None
enabled: bool = True
class ASRModelCreate(ASRModelBase):
id: Optional[str] = None
hotwords: List[str] = []
enable_punctuation: bool = True
enable_normalization: bool = True
class ASRModelUpdate(BaseModel):
name: Optional[str] = None
language: Optional[str] = None
base_url: Optional[str] = None
api_key: Optional[str] = None
model_name: Optional[str] = None
hotwords: Optional[List[str]] = None
enable_punctuation: Optional[bool] = None
enable_normalization: Optional[bool] = None
enabled: Optional[bool] = None
class ASRModelOut(ASRModelBase):
id: str
user_id: int
hotwords: List[str] = []
enable_punctuation: bool = True
enable_normalization: bool = True
created_at: Optional[datetime] = None
class Config:
from_attributes = True
class ASRTestRequest(BaseModel):
audio_url: Optional[str] = None
audio_data: Optional[str] = None # base64 encoded
class ASRTestResponse(BaseModel):
success: bool
transcript: Optional[str] = None
language: Optional[str] = None
confidence: Optional[float] = None
duration_ms: Optional[int] = None
latency_ms: Optional[int] = None
message: Optional[str] = None
error: Optional[str] = None
# ============ Assistant ============
class AssistantBase(BaseModel):
name: str
opener: str = ""
prompt: str = ""
knowledgeBaseId: Optional[str] = None
language: str = "zh"
voice: Optional[str] = None
speed: float = 1.0
hotwords: List[str] = []
tools: List[str] = []
interruptionSensitivity: int = 500
configMode: str = "platform"
apiUrl: Optional[str] = None
apiKey: Optional[str] = None
# 模型关联
llmModelId: Optional[str] = None
asrModelId: Optional[str] = None
embeddingModelId: Optional[str] = None
rerankModelId: Optional[str] = None
class AssistantCreate(AssistantBase):
pass
class AssistantUpdate(BaseModel):
name: Optional[str] = None
opener: Optional[str] = None
prompt: Optional[str] = None
knowledgeBaseId: Optional[str] = None
language: Optional[str] = None
voice: Optional[str] = None
speed: Optional[float] = None
hotwords: Optional[List[str]] = None
tools: Optional[List[str]] = None
interruptionSensitivity: Optional[int] = None
configMode: Optional[str] = None
apiUrl: Optional[str] = None
apiKey: Optional[str] = None
llmModelId: Optional[str] = None
asrModelId: Optional[str] = None
embeddingModelId: Optional[str] = None
rerankModelId: Optional[str] = None
class AssistantOut(AssistantBase):
id: str
callCount: int = 0
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class AssistantStats(BaseModel):
assistant_id: str
total_calls: int = 0
connected_calls: int = 0
missed_calls: int = 0
avg_duration_seconds: float = 0.0
today_calls: int = 0
# ============ Knowledge Base ============
class KnowledgeDocument(BaseModel):
id: str
name: str
size: str
fileType: str = "txt"
storageUrl: Optional[str] = None
status: str = "pending"
chunkCount: int = 0
uploadDate: str
class KnowledgeDocumentCreate(BaseModel):
name: str
size: str
fileType: str = "txt"
storageUrl: Optional[str] = None
class KnowledgeDocumentUpdate(BaseModel):
status: Optional[str] = None
chunkCount: Optional[int] = None
errorMessage: Optional[str] = None
class KnowledgeBaseBase(BaseModel):
name: str
description: str = ""
embeddingModel: str = "text-embedding-3-small"
chunkSize: int = 500
chunkOverlap: int = 50
class KnowledgeBaseCreate(KnowledgeBaseBase):
pass
class KnowledgeBaseUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
embeddingModel: Optional[str] = None
chunkSize: Optional[int] = None
chunkOverlap: Optional[int] = None
status: Optional[str] = None
class KnowledgeBaseOut(KnowledgeBaseBase):
id: str
docCount: int = 0
chunkCount: int = 0
status: str = "active"
createdAt: Optional[datetime] = None
updatedAt: Optional[datetime] = None
documents: List[KnowledgeDocument] = []
class Config:
from_attributes = True
# ============ Knowledge Search ============
class KnowledgeSearchQuery(BaseModel):
query: str
kb_id: str
nResults: int = 5
class KnowledgeSearchResult(BaseModel):
query: str
results: List[dict]
class DocumentIndexRequest(BaseModel):
document_id: str
content: str
class KnowledgeStats(BaseModel):
kb_id: str
docCount: int
chunkCount: int
# ============ Workflow ============
class WorkflowNode(BaseModel):
name: str
type: str
isStart: Optional[bool] = None
metadata: dict
prompt: Optional[str] = None
messagePlan: Optional[dict] = None
variableExtractionPlan: Optional[dict] = None
tool: Optional[dict] = None
globalNodePlan: Optional[dict] = None
class WorkflowEdge(BaseModel):
from_: str
to: str
label: Optional[str] = None
class Config:
populate_by_name = True
class WorkflowBase(BaseModel):
name: str
nodeCount: int = 0
createdAt: str = ""
updatedAt: str = ""
globalPrompt: Optional[str] = None
nodes: List[dict] = []
edges: List[dict] = []
class WorkflowCreate(WorkflowBase):
pass
class WorkflowUpdate(BaseModel):
name: Optional[str] = None
nodeCount: Optional[int] = None
nodes: Optional[List[dict]] = None
edges: Optional[List[dict]] = None
globalPrompt: Optional[str] = None
class WorkflowOut(WorkflowBase):
id: str
class Config:
from_attributes = True
# ============ Call Record ============
class TranscriptSegment(BaseModel):
turnIndex: int
speaker: str # human/ai
content: str
confidence: Optional[float] = None
startMs: int
endMs: int
durationMs: Optional[int] = None
audioUrl: Optional[str] = None
emotion: Optional[str] = None
class CallRecordCreate(BaseModel):
user_id: int
assistant_id: Optional[str] = None
source: str = "debug"
status: Optional[str] = None
cost: Optional[float] = None
class CallRecordUpdate(BaseModel):
status: Optional[str] = None
summary: Optional[str] = None
duration_seconds: Optional[int] = None
ended_at: Optional[str] = None
cost: Optional[float] = None
metadata: Optional[dict] = None
class CallRecordOut(BaseModel):
id: str
user_id: int
assistant_id: Optional[str] = None
source: str
status: str
started_at: str
ended_at: Optional[str] = None
duration_seconds: Optional[int] = None
summary: Optional[str] = None
cost: float = 0.0
metadata: dict = {}
created_at: Optional[datetime] = None
transcripts: List[TranscriptSegment] = []
class Config:
from_attributes = True
# ============ Call Transcript ============
class TranscriptCreate(BaseModel):
turn_index: int
speaker: str
content: str
confidence: Optional[float] = None
start_ms: int
end_ms: int
duration_ms: Optional[int] = None
emotion: Optional[str] = None
class TranscriptOut(TranscriptCreate):
id: int
audio_url: Optional[str] = None
class Config:
from_attributes = True
# ============ History Stats ============
class HistoryStats(BaseModel):
total_calls: int = 0
connected_calls: int = 0
missed_calls: int = 0
failed_calls: int = 0
avg_duration_seconds: float = 0.0
total_cost: float = 0.0
by_status: dict = {}
by_source: dict = {}
daily_trend: List[dict] = []
# ============ Dashboard ============
class DashboardStats(BaseModel):
totalCalls: int
answerRate: int
avgDuration: str
humanTransferCount: int
trend: List[dict]
# ============ API Response ============
class Message(BaseModel):
message: str
class DocumentIndexRequest(BaseModel):
content: str
class ListResponse(BaseModel):
total: int
page: int
limit: int
list: List
class SearchResult(BaseModel):
id: str
started_at: str
matched_content: Optional[str] = None