- Introduce new fields for voice, speed, and language in the AssistantConfig and ProviderCredential models to support TTS and ASR configurations. - Update the database schema and seeding script to accommodate the new fields, ensuring backward compatibility. - Implement credential testing endpoints and logic to validate OpenAI-compatible credentials, enhancing user experience and reliability. - Modify frontend components to include new fields in the credential forms and improve connection testing feedback. - Refactor related services and API interactions to support the new credential testing feature.
149 lines
4.5 KiB
Python
149 lines
4.5 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=(),
|
|
)
|
|
|
|
|
|
# 各 type 允许的瘦字段(其余字段写入时清零,防止跨类型脏数据)
|
|
ALLOWED_FIELDS: dict[str, set[str]] = {
|
|
"prompt": {"prompt"},
|
|
"workflow": {"graph"},
|
|
"dify": {"api_url", "api_key"},
|
|
"fastgpt": {"app_id", "api_url", "api_key"},
|
|
"opencode": {"prompt", "api_url", "api_key"},
|
|
}
|
|
|
|
|
|
# ---------- 助手(单表 STI:瘦类型真列 + workflow 图 JSON 列) ----------
|
|
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
|
|
|
|
# 瘦类型专属(真列);按 type 取用,无关字段写入时清零
|
|
prompt: str = ""
|
|
api_url: str = ""
|
|
api_key: str = "" # 写时:占位符/空 → 保留旧(哨兵)
|
|
app_id: str = ""
|
|
# workflow 专属:图
|
|
graph: dict[str, Any] = {}
|
|
|
|
@model_validator(mode="after")
|
|
def _strip_irrelevant_fields(self):
|
|
allowed = ALLOWED_FIELDS[self.type]
|
|
for field in ("prompt", "api_url", "api_key", "app_id"):
|
|
if field not in allowed:
|
|
setattr(self, field, "")
|
|
if "graph" not in allowed:
|
|
self.graph = {}
|
|
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 = "" # 写时:占位符/空表示不改
|
|
voice: str = "" # TTS
|
|
speed: float = 1.0 # TTS
|
|
language: str = "" # ASR
|
|
is_default: bool = False
|
|
|
|
@model_validator(mode="after")
|
|
def _strip_irrelevant_options(self):
|
|
if self.type != "TTS":
|
|
self.voice = ""
|
|
self.speed = 1.0
|
|
if self.type != "ASR":
|
|
self.language = ""
|
|
return self
|
|
|
|
|
|
class CredentialOut(CamelModel):
|
|
id: str
|
|
name: str
|
|
model_id: str
|
|
type: str
|
|
interface_type: str
|
|
api_url: str
|
|
api_key: str # 读时:打码后的值
|
|
voice: str
|
|
speed: float
|
|
language: str
|
|
is_default: bool
|
|
|
|
|
|
class CredentialTestRequest(CamelModel):
|
|
model_id: str
|
|
type: ModelType
|
|
interface_type: InterfaceType = "openai"
|
|
api_url: str
|
|
api_key: str = ""
|
|
voice: str = ""
|
|
speed: float = 1.0
|
|
language: str = ""
|
|
|
|
|
|
class CredentialTestResult(CamelModel):
|
|
ok: bool
|
|
latency_ms: int | None = None
|
|
message: str
|
|
detail: str = ""
|