Refactor backend to support interface-definition driven model resources

- Introduce a new model structure for managing interface definitions and model resources, enhancing the backend's capability to handle various service integrations.
- Update the Makefile to reflect changes in database seeding and resource management commands.
- Remove the deprecated credentials management routes and replace them with a unified model registry API.
- Modify existing routes and schemas to align with the new model structure, ensuring seamless integration with the frontend.
- Enhance database seeding scripts to populate new model resources and their configurations.
- Update README documentation to reflect the new architecture and usage instructions for model resources and interface definitions.
This commit is contained in:
Xin Wang
2026-06-14 19:36:12 +08:00
parent e25dfd4003
commit 90e3e8a0c0
32 changed files with 2577 additions and 1765 deletions

View File

@@ -1,15 +1,16 @@
"""数据表定义(SQLAlchemy 2.0)。
两张表,职责分离(见设计):
- ProviderCredential:模型凭证(key 明文存,同 dograh,靠 DB 访问控制兜底;读时打码)
- Assistant:助手配置,**只存模型/音色的"选项名",不嵌 key**
模型注册表由接口定义驱动:
- InterfaceDefinition:具体接入协议及其动态表单字段
- ModelResource:模型配置与鉴权值
- AssistantModelBinding:助手按能力选择模型资源
助手运行时再用 kind 去 ProviderCredential 取真 key(services/config_resolver.py)。
"""
from datetime import datetime
from sqlalchemy import JSON, Boolean, DateTime, Float, ForeignKey, String, func
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, String, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -17,22 +18,39 @@ class Base(DeclarativeBase):
pass
class ProviderCredential(Base):
"""模型资源凭证。字段对齐前端 ComponentsModelsPage 的 ModelResource"""
class InterfaceDefinition(Base):
"""具体接入协议,例如 xfyun-tts 与 xfyun-super-tts"""
__tablename__ = "provider_credentials"
__tablename__ = "interface_definitions"
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="") # 明文
voice: Mapped[str] = mapped_column(String(128), default="") # TTS 音色
speed: Mapped[float] = mapped_column(Float, default=1.0) # TTS 语速
language: Mapped[str] = mapped_column(String(32), default="") # ASR 语言
# 同一 type 下的默认凭证(后端解析用;前端 ModelResource 无此字段,留作可选)
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)
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(
@@ -41,7 +59,7 @@ class ProviderCredential(Base):
class KnowledgeBase(Base):
"""知识库注册表。本身引用一个 Embedding 凭证(用哪个向量模型)
"""知识库注册表。本身引用一个 Embedding 模型资源
文档/分块(pgvector)是 KB 内部实现,这里先不展开;助手侧只认 knowledge_base_id。
"""
@@ -51,10 +69,9 @@ class KnowledgeBase(Base):
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(
embedding_model_resource_id: Mapped[str | None] = mapped_column(
String(40),
ForeignKey("provider_credentials.id", ondelete="SET NULL"),
ForeignKey("model_resources.id", ondelete="SET NULL"),
nullable=True,
)
status: Mapped[str] = mapped_column(String(16), default="active") # active|archived
@@ -80,19 +97,6 @@ class Assistant(Base):
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
@@ -101,7 +105,7 @@ class Assistant(Base):
# ---- 瘦类型专属字段(真列,稀疏:按 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(打码/哨兵,同凭证)
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)
@@ -110,3 +114,26 @@ class Assistant(Base):
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()
)