"""数据表定义(SQLAlchemy 2.0)。 两张表,职责分离(见设计): - ProviderCredential:模型凭证(key 明文存,同 dograh,靠 DB 访问控制兜底;读时打码) - Assistant:助手配置,**只存模型/音色的"选项名",不嵌 key** 助手运行时再用 kind 去 ProviderCredential 取真 key(services/config_resolver.py)。 """ from datetime import datetime from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, String, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): pass class ProviderCredential(Base): """模型资源凭证。字段对齐前端 ComponentsModelsPage 的 ModelResource。""" __tablename__ = "provider_credentials" id: Mapped[str] = mapped_column(String(40), primary_key=True) # model_xxx name: Mapped[str] = mapped_column(String(128), default="") # 资源名称,如 "DeepSeek-V3" model_id: Mapped[str] = mapped_column(String(128), default="") # 模型ID,如 "deepseek-chat" type: Mapped[str] = mapped_column(String(16), index=True) # LLM|ASR|TTS|Realtime|Embedding interface_type: Mapped[str] = mapped_column(String(32), default="openai") # openai|xfyun|dashscope|gemini api_url: Mapped[str] = mapped_column(String(512), default="") api_key: Mapped[str] = mapped_column(String(512), default="") # 明文 # 同一 type 下的默认凭证(后端解析用;前端 ModelResource 无此字段,留作可选) is_default: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) class KnowledgeBase(Base): """知识库注册表。本身引用一个 Embedding 凭证(用哪个向量模型)。 文档/分块(pgvector)是 KB 内部实现,这里先不展开;助手侧只认 knowledge_base_id。 """ __tablename__ = "knowledge_bases" id: Mapped[str] = mapped_column(String(40), primary_key=True) # kb_xxx name: Mapped[str] = mapped_column(String(128)) description: Mapped[str] = mapped_column(String(2048), default="") # 该 KB 用哪个向量模型;凭证被删则置空 embedding_credential_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("provider_credentials.id", ondelete="SET NULL"), nullable=True, ) status: Mapped[str] = mapped_column(String(16), default="active") # active|archived created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) class Assistant(Base): """助手(单表,无版本化)。type 为可变普通列,5 种类型共用此表。 模型/KB 以 FK 引用注册表;类型专属字段塞进 config(JSON)。 """ __tablename__ = "assistants" id: Mapped[str] = mapped_column(String(40), primary_key=True) # asst_xxx name: Mapped[str] = mapped_column(String(128)) # prompt|workflow|dify|fastgpt|opencode;创建后可改 type: Mapped[str] = mapped_column(String(16), index=True, default="prompt") runtime_mode: Mapped[str] = mapped_column(String(16), default="pipeline") greeting: Mapped[str] = mapped_column(String(2048), default="") enable_interrupt: Mapped[bool] = mapped_column(Boolean, default=True) # ---- 引用"注册好的资源":凭证被删 → SET NULL(resolver 有默认/.env 兜底) ---- llm_credential_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("provider_credentials.id", ondelete="SET NULL"), nullable=True ) asr_credential_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("provider_credentials.id", ondelete="SET NULL"), nullable=True ) tts_credential_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("provider_credentials.id", ondelete="SET NULL"), nullable=True ) realtime_credential_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("provider_credentials.id", ondelete="SET NULL"), nullable=True ) # KB 引用:被引用时禁止删 KB(RESTRICT),无默认兜底 knowledge_base_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("knowledge_bases.id", ondelete="RESTRICT"), nullable=True ) # ---- 瘦类型专属字段(真列,稀疏:按 type 用其中几列) ---- prompt: Mapped[str] = mapped_column(String(8192), default="") # prompt / opencode api_url: Mapped[str] = mapped_column(String(512), default="") # dify / fastgpt / opencode api_key: Mapped[str] = mapped_column(String(512), default="") # dify / fastgpt / opencode(打码/哨兵,同凭证) app_id: Mapped[str] = mapped_column(String(128), default="") # fastgpt # workflow 专属:图(nodes/edges)。要版本化时再迁出到 assistant_workflow 表 graph: Mapped[dict] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() )