Add CRUD functionality for knowledge bases, including routes for listing, creating, updating, and deleting knowledge bases. Update the assistant model to include foreign key references to knowledge bases and modify the assistant configuration to handle external API keys securely. Refactor related services and routes to accommodate these changes, ensuring proper handling of credential resolution and configuration normalization.
134 lines
3.9 KiB
Python
134 lines
3.9 KiB
Python
"""面向前端的请求/响应 DTO。与 DB 模型解耦,**响应里的 key 一律打码**。
|
|
|
|
凭证 DTO 字段对齐前端 ComponentsModelsPage 的 ModelResource:
|
|
JSON 用 camelCase(modelId/interfaceType/apiUrl/apiKey),Python 内部用 snake_case,
|
|
靠 Pydantic alias 自动互转。FastAPI 响应默认 by_alias=True,所以出参也是 camelCase。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, model_validator
|
|
from pydantic.alias_generators import to_camel
|
|
|
|
RuntimeMode = Literal["pipeline", "realtime"]
|
|
ModelType = Literal["LLM", "ASR", "TTS", "Realtime", "Embedding"]
|
|
InterfaceType = Literal["openai", "xfyun", "dashscope", "gemini"]
|
|
AssistantType = Literal["prompt", "workflow", "dify", "fastgpt", "opencode"]
|
|
|
|
# 外部应用类型:其 config.apiKey 是该助手私有密钥,读时打码 / 写时哨兵
|
|
EXTERNAL_TYPES = {"dify", "fastgpt", "opencode"}
|
|
|
|
|
|
class CamelModel(BaseModel):
|
|
"""JSON camelCase ↔ Python snake_case。protected_namespaces 关掉以允许 model_id。"""
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
protected_namespaces=(),
|
|
)
|
|
|
|
|
|
# ---------- 各类型的 config 形态(JSON 内嵌,按 type 校验) ----------
|
|
class PromptConfig(CamelModel):
|
|
prompt: str = ""
|
|
realtime_model: str = ""
|
|
|
|
|
|
class WorkflowConfig(CamelModel):
|
|
graph: dict[str, Any] = {} # {nodes, edges, viewport};节点 data 可带 *CredentialId 覆盖
|
|
|
|
|
|
class DifyConfig(CamelModel):
|
|
api_url: str = ""
|
|
api_key: str = "" # 写时:占位符/空 → 保留旧
|
|
|
|
|
|
class FastgptConfig(CamelModel):
|
|
app_id: str = ""
|
|
api_url: str = ""
|
|
api_key: str = ""
|
|
|
|
|
|
class OpencodeConfig(CamelModel):
|
|
prompt: str = ""
|
|
api_url: str = ""
|
|
api_key: str = ""
|
|
|
|
|
|
CONFIG_BY_TYPE: dict[str, type[CamelModel]] = {
|
|
"prompt": PromptConfig,
|
|
"workflow": WorkflowConfig,
|
|
"dify": DifyConfig,
|
|
"fastgpt": FastgptConfig,
|
|
"opencode": OpencodeConfig,
|
|
}
|
|
|
|
|
|
# ---------- 助手(单表,无版本化;type 可变) ----------
|
|
class AssistantUpsert(CamelModel):
|
|
name: str
|
|
type: AssistantType = "prompt"
|
|
runtime_mode: RuntimeMode = "pipeline"
|
|
greeting: str = ""
|
|
enable_interrupt: bool = True
|
|
|
|
# 引用注册资源(FK id;None=未选)
|
|
llm_credential_id: str | None = None
|
|
asr_credential_id: str | None = None
|
|
tts_credential_id: str | None = None
|
|
realtime_credential_id: str | None = None
|
|
knowledge_base_id: str | None = None
|
|
|
|
# 类型专属字段;校验后归一为 camelCase 存库
|
|
config: dict[str, Any] = {}
|
|
|
|
@model_validator(mode="after")
|
|
def _normalize_config(self):
|
|
model = CONFIG_BY_TYPE[self.type]
|
|
# 按当前 type 校验并裁掉无关字段,统一回 camelCase 落库
|
|
self.config = model.model_validate(self.config).model_dump(by_alias=True)
|
|
return self
|
|
|
|
|
|
class AssistantOut(AssistantUpsert):
|
|
id: str
|
|
updated_at: str | None = None
|
|
|
|
|
|
# ---------- 知识库 ----------
|
|
class KnowledgeBaseUpsert(CamelModel):
|
|
name: str
|
|
description: str = ""
|
|
embedding_credential_id: str | None = None
|
|
|
|
|
|
class KnowledgeBaseOut(KnowledgeBaseUpsert):
|
|
id: str
|
|
status: str = "active"
|
|
updated_at: str | None = None
|
|
|
|
|
|
# ---------- 模型凭证(对齐前端 ModelResource) ----------
|
|
class CredentialUpsert(CamelModel):
|
|
name: str = "" # 资源名称
|
|
model_id: str = "" # 模型ID
|
|
type: ModelType # LLM/ASR/TTS/Realtime/Embedding
|
|
interface_type: InterfaceType = "openai" # openai/xfyun/dashscope/gemini
|
|
api_url: str = ""
|
|
api_key: str = "" # 写时:占位符/空表示不改
|
|
is_default: bool = False
|
|
|
|
|
|
class CredentialOut(CamelModel):
|
|
id: str
|
|
name: str
|
|
model_id: str
|
|
type: str
|
|
interface_type: str
|
|
api_url: str
|
|
api_key: str # 读时:打码后的值
|
|
is_default: bool
|