278 lines
15 KiB
Python
278 lines
15 KiB
Python
from datetime import datetime
|
|
from typing import List, Optional
|
|
from sqlalchemy import String, Integer, DateTime, Text, Float, ForeignKey, JSON, Enum
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .db import Base
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
|
|
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
# ============ Voice ============
|
|
class Voice(Base):
|
|
__tablename__ = "voices"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), index=True, nullable=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
gender: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
language: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
description: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
model: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商语音模型标识
|
|
voice_key: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) # 厂商voice_key
|
|
api_key: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) # 每个声音独立 API key
|
|
base_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) # 每个声音独立 OpenAI-compatible base_url
|
|
speed: Mapped[float] = mapped_column(Float, default=1.0)
|
|
gain: Mapped[int] = mapped_column(Integer, default=0)
|
|
pitch: Mapped[int] = mapped_column(Integer, default=0)
|
|
enabled: Mapped[bool] = mapped_column(default=True)
|
|
is_system: Mapped[bool] = mapped_column(default=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User", foreign_keys=[user_id])
|
|
|
|
|
|
# ============ LLM Model ============
|
|
class LLMModel(Base):
|
|
__tablename__ = "llm_models"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
type: Mapped[str] = mapped_column(String(32), nullable=False) # text/embedding/rerank
|
|
base_url: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
api_key: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
model_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
|
temperature: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
context_length: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
enabled: Mapped[bool] = mapped_column(default=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
|
|
|
|
# ============ ASR Model ============
|
|
class ASRModel(Base):
|
|
__tablename__ = "asr_models"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
vendor: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
language: Mapped[str] = mapped_column(String(32), nullable=False) # zh/en/Multi-lingual
|
|
base_url: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
api_key: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
model_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
|
hotwords: Mapped[dict] = mapped_column(JSON, default=list)
|
|
enable_punctuation: Mapped[bool] = mapped_column(default=True)
|
|
enable_normalization: Mapped[bool] = mapped_column(default=True)
|
|
enabled: Mapped[bool] = mapped_column(default=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
|
|
|
|
# ============ Tool Resource ============
|
|
class ToolResource(Base):
|
|
__tablename__ = "tool_resources"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), index=True, nullable=True)
|
|
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
description: Mapped[str] = mapped_column(String(512), nullable=False, default="")
|
|
category: Mapped[str] = mapped_column(String(32), nullable=False, default="system") # system/query
|
|
icon: Mapped[str] = mapped_column(String(64), nullable=False, default="Wrench")
|
|
http_method: Mapped[str] = mapped_column(String(16), nullable=False, default="GET")
|
|
http_url: Mapped[Optional[str]] = mapped_column(String(1024), nullable=True)
|
|
http_headers: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
http_timeout_ms: Mapped[int] = mapped_column(Integer, default=10000)
|
|
parameter_schema: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
parameter_defaults: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
wait_for_response: Mapped[bool] = mapped_column(default=False)
|
|
enabled: Mapped[bool] = mapped_column(default=True)
|
|
is_system: Mapped[bool] = mapped_column(default=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
|
|
|
|
# ============ Assistant ============
|
|
class Assistant(Base):
|
|
__tablename__ = "assistants"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
call_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
first_turn_mode: Mapped[str] = mapped_column(String(32), default="bot_first")
|
|
opener: Mapped[str] = mapped_column(Text, default="")
|
|
generated_opener_enabled: Mapped[bool] = mapped_column(default=False)
|
|
prompt: Mapped[str] = mapped_column(Text, default="")
|
|
knowledge_base_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
language: Mapped[str] = mapped_column(String(16), default="zh")
|
|
voice_output_enabled: Mapped[bool] = mapped_column(default=True)
|
|
voice: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
speed: Mapped[float] = mapped_column(Float, default=1.0)
|
|
hotwords: Mapped[dict] = mapped_column(JSON, default=list)
|
|
tools: Mapped[dict] = mapped_column(JSON, default=list)
|
|
bot_cannot_be_interrupted: Mapped[bool] = mapped_column(default=False)
|
|
interruption_sensitivity: Mapped[int] = mapped_column(Integer, default=500)
|
|
config_mode: Mapped[str] = mapped_column(String(32), default="platform")
|
|
api_url: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
|
api_key: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
|
# 模型关联
|
|
llm_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
asr_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
embedding_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
rerank_model_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
call_records = relationship("CallRecord", back_populates="assistant")
|
|
opener_audio = relationship("AssistantOpenerAudio", back_populates="assistant", uselist=False, cascade="all, delete-orphan")
|
|
|
|
|
|
class AssistantOpenerAudio(Base):
|
|
__tablename__ = "assistant_opener_audio"
|
|
|
|
assistant_id: Mapped[str] = mapped_column(String(64), ForeignKey("assistants.id"), primary_key=True)
|
|
enabled: Mapped[bool] = mapped_column(default=False)
|
|
file_path: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
|
encoding: Mapped[str] = mapped_column(String(32), default="pcm_s16le")
|
|
sample_rate_hz: Mapped[int] = mapped_column(Integer, default=16000)
|
|
channels: Mapped[int] = mapped_column(Integer, default=1)
|
|
duration_ms: Mapped[int] = mapped_column(Integer, default=0)
|
|
text_hash: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
|
tts_fingerprint: Mapped[Optional[str]] = mapped_column(String(256), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
assistant = relationship("Assistant", back_populates="opener_audio")
|
|
|
|
|
|
# ============ Knowledge Base ============
|
|
class KnowledgeBase(Base):
|
|
__tablename__ = "knowledge_bases"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str] = mapped_column(Text, default="")
|
|
embedding_model: Mapped[str] = mapped_column(String(64), default="text-embedding-3-small")
|
|
chunk_size: Mapped[int] = mapped_column(Integer, default=500)
|
|
chunk_overlap: Mapped[int] = mapped_column(Integer, default=50)
|
|
doc_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
chunk_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
status: Mapped[str] = mapped_column(String(32), default="active")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
documents = relationship("KnowledgeDocument", back_populates="kb")
|
|
|
|
|
|
class KnowledgeDocument(Base):
|
|
__tablename__ = "knowledge_documents"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
kb_id: Mapped[str] = mapped_column(String(64), ForeignKey("knowledge_bases.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
size: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
file_type: Mapped[str] = mapped_column(String(32), default="txt")
|
|
storage_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(32), default="pending") # pending/processing/completed/failed
|
|
chunk_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
upload_date: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
processed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
|
|
|
kb = relationship("KnowledgeBase", back_populates="documents")
|
|
|
|
|
|
# ============ Workflow ============
|
|
class Workflow(Base):
|
|
__tablename__ = "workflows"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
node_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
created_at: Mapped[str] = mapped_column(String(32), default="")
|
|
updated_at: Mapped[str] = mapped_column(String(32), default="")
|
|
global_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
nodes: Mapped[dict] = mapped_column(JSON, default=list)
|
|
edges: Mapped[dict] = mapped_column(JSON, default=list)
|
|
|
|
user = relationship("User")
|
|
|
|
|
|
# ============ Call Record ============
|
|
class CallRecord(Base):
|
|
__tablename__ = "call_records"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
assistant_id: Mapped[Optional[str]] = mapped_column(String(64), ForeignKey("assistants.id"), index=True)
|
|
source: Mapped[str] = mapped_column(String(32), default="debug")
|
|
status: Mapped[str] = mapped_column(String(32), default="connected")
|
|
started_at: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
ended_at: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
|
duration_seconds: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
cost: Mapped[float] = mapped_column(Float, default=0.0)
|
|
call_metadata: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
user = relationship("User")
|
|
assistant = relationship("Assistant", back_populates="call_records")
|
|
transcripts = relationship("CallTranscript", back_populates="call_record")
|
|
audio_segments = relationship("CallAudioSegment", back_populates="call_record")
|
|
|
|
|
|
class CallTranscript(Base):
|
|
__tablename__ = "call_transcripts"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
call_id: Mapped[str] = mapped_column(String(64), ForeignKey("call_records.id"), index=True)
|
|
turn_index: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
speaker: Mapped[str] = mapped_column(String(16), nullable=False) # human/ai
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
|
start_ms: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
end_ms: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
emotion: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
|
|
|
call_record = relationship("CallRecord", back_populates="transcripts")
|
|
|
|
|
|
class CallAudioSegment(Base):
|
|
__tablename__ = "call_audio_segments"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
call_id: Mapped[str] = mapped_column(String(64), ForeignKey("call_records.id"), index=True)
|
|
transcript_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("call_transcripts.id"), nullable=True)
|
|
turn_index: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
audio_url: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
audio_format: Mapped[str] = mapped_column(String(16), default="mp3")
|
|
file_size_bytes: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
start_ms: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
end_ms: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
|
|
call_record = relationship("CallRecord", back_populates="audio_segments")
|