"""数据表定义(SQLAlchemy 2.0)。 模型注册表由接口定义驱动: - InterfaceDefinition:具体接入协议及其动态表单字段 - ModelResource:模型配置与鉴权值 - AssistantModelBinding:助手按能力选择模型资源 """ from datetime import datetime from sqlalchemy import ( JSON, Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): pass class InterfaceDefinition(Base): """具体接入协议,例如 xfyun-tts 与 xfyun-super-tts。""" __tablename__ = "interface_definitions" interface_type: Mapped[str] = mapped_column(String(64), primary_key=True) name: Mapped[str] = mapped_column(String(128)) capability: Mapped[str] = mapped_column(String(16), index=True) field_schema: Mapped[dict] = mapped_column(JSONB, default=dict) enabled: Mapped[bool] = mapped_column(Boolean, default=True) version: Mapped[int] = mapped_column(default=1) 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 ModelResource(Base): """统一模型资源:接口类型决定能力、鉴权字段和调用参数。""" __tablename__ = "model_resources" id: Mapped[str] = mapped_column(String(40), primary_key=True) name: Mapped[str] = mapped_column(String(128), default="") capability: Mapped[str] = mapped_column(String(16), index=True) interface_type: Mapped[str] = mapped_column( String(64), ForeignKey("interface_definitions.interface_type", ondelete="RESTRICT"), index=True, ) values: Mapped[dict] = mapped_column(JSONB, default=dict) secrets: Mapped[dict] = mapped_column(JSONB, default=dict) support_image_input: Mapped[bool] = mapped_column(Boolean, default=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True) 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="") embedding_model_resource_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("model_resources.id", ondelete="SET NULL"), index=True, 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) vision_enabled: Mapped[bool] = mapped_column(Boolean, default=False) vision_model_resource_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("model_resources.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() ) class AssistantModelBinding(Base): """助手按能力绑定统一模型资源;config 可覆盖资源默认 options。""" __tablename__ = "assistant_model_bindings" assistant_id: Mapped[str] = mapped_column( String(40), ForeignKey("assistants.id", ondelete="CASCADE"), primary_key=True, ) capability: Mapped[str] = mapped_column(String(16), primary_key=True) model_resource_id: Mapped[str] = mapped_column( String(40), ForeignKey("model_resources.id", ondelete="RESTRICT"), index=True, ) config: Mapped[dict] = mapped_column(JSONB, 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() ) class Tool(Base): """Reusable LLM tool definition; supported types are executed at runtime.""" __tablename__ = "tools" id: Mapped[str] = mapped_column(String(40), primary_key=True) name: Mapped[str] = mapped_column(String(128)) function_name: Mapped[str] = mapped_column(String(64), unique=True, index=True) type: Mapped[str] = mapped_column(String(24), index=True) description: Mapped[str] = mapped_column(String(2048), default="") definition: Mapped[dict] = mapped_column(JSONB, default=dict) secrets: Mapped[dict] = mapped_column(JSONB, default=dict) status: Mapped[str] = mapped_column(String(16), index=True, default="active") 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 AssistantToolBinding(Base): """Prompt assistant to reusable tool many-to-many binding.""" __tablename__ = "assistant_tool_bindings" assistant_id: Mapped[str] = mapped_column( String(40), ForeignKey("assistants.id", ondelete="CASCADE"), primary_key=True, ) tool_id: Mapped[str] = mapped_column( String(40), ForeignKey("tools.id", ondelete="RESTRICT"), primary_key=True, index=True, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() ) class ConversationSession(Base): """一次完整连接对应的对话会话;媒体文件以后作为 artifact 关联进来。""" __tablename__ = "conversation_sessions" id: Mapped[str] = mapped_column(String(40), primary_key=True) assistant_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("assistants.id", ondelete="SET NULL"), nullable=True, index=True, ) assistant_name: Mapped[str] = mapped_column(String(128), default="") channel: Mapped[str] = mapped_column(String(24), index=True) runtime_mode: Mapped[str] = mapped_column(String(16), default="pipeline") status: Mapped[str] = mapped_column(String(16), index=True, default="active") message_count: Mapped[int] = mapped_column(Integer, default=0) started_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), index=True ) ended_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) extra: Mapped[dict] = mapped_column(JSONB, default=dict) class ConversationMessage(Base): """会话中的有序消息;当前只写 text,结构预留其他内容类型。""" __tablename__ = "conversation_messages" __table_args__ = ( UniqueConstraint( "session_id", "sequence", name="uq_conversation_message_sequence" ), ) id: Mapped[str] = mapped_column(String(40), primary_key=True) session_id: Mapped[str] = mapped_column( String(40), ForeignKey("conversation_sessions.id", ondelete="CASCADE"), index=True, ) sequence: Mapped[int] = mapped_column(Integer) role: Mapped[str] = mapped_column(String(16)) content_type: Mapped[str] = mapped_column(String(16), default="text") content: Mapped[str] = mapped_column(Text, default="") occurred_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() ) extra: Mapped[dict] = mapped_column(JSONB, default=dict) class ConversationArtifact(Base): """未来的音频、视频、图片等大对象索引;二进制本体不进入数据库。""" __tablename__ = "conversation_artifacts" id: Mapped[str] = mapped_column(String(40), primary_key=True) session_id: Mapped[str] = mapped_column( String(40), ForeignKey("conversation_sessions.id", ondelete="CASCADE"), index=True, ) message_id: Mapped[str | None] = mapped_column( String(40), ForeignKey("conversation_messages.id", ondelete="SET NULL"), nullable=True, index=True, ) kind: Mapped[str] = mapped_column(String(16), index=True) storage_uri: Mapped[str] = mapped_column(String(1024)) mime_type: Mapped[str] = mapped_column(String(128), default="") size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now() ) extra: Mapped[dict] = mapped_column(JSONB, default=dict)