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:
@@ -1,52 +1,63 @@
|
||||
"""assistant_id → 运行时配置(把真 key 在服务端组装好)。
|
||||
|
||||
浏览器只传 assistant_id;真 key 在这里从 provider_credentials 取出注入。
|
||||
助手按 FK(*_credential_id)引用凭证;取不到则回退该 type 默认凭证,再回退 .env。
|
||||
浏览器只传 assistant_id;真 key 在这里从 model_resources 取出注入。
|
||||
助手按 capability binding 引用资源;取不到则回退该能力默认资源,再回退 .env。
|
||||
"""
|
||||
|
||||
import config
|
||||
from db.models import Assistant, ProviderCredential
|
||||
from db.models import Assistant, AssistantModelBinding, ModelResource
|
||||
from models import AssistantConfig
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
async def _default_credential(
|
||||
session: AsyncSession, type_: str
|
||||
) -> ProviderCredential | None:
|
||||
"""该 type 的默认凭证(is_default 优先,否则按 id 取第一条)。"""
|
||||
stmt = (
|
||||
select(ProviderCredential)
|
||||
.where(ProviderCredential.type == type_)
|
||||
.order_by(ProviderCredential.is_default.desc(), ProviderCredential.id.asc())
|
||||
.limit(1)
|
||||
)
|
||||
return (await session.execute(stmt)).scalar_one_or_none()
|
||||
async def _resource_for(
|
||||
session: AsyncSession,
|
||||
assistant_id: str,
|
||||
capability: str,
|
||||
) -> ModelResource | None:
|
||||
binding = await session.get(AssistantModelBinding, (assistant_id, capability))
|
||||
resource_id = binding.model_resource_id if binding else None
|
||||
resource = await session.get(ModelResource, resource_id) if resource_id else None
|
||||
if resource and resource.capability != capability:
|
||||
resource = None
|
||||
if resource is None:
|
||||
resource = (
|
||||
await session.execute(
|
||||
select(ModelResource)
|
||||
.where(ModelResource.capability == capability, ModelResource.enabled.is_(True))
|
||||
.order_by(ModelResource.is_default.desc(), ModelResource.id.asc())
|
||||
.limit(1)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
return resource
|
||||
|
||||
|
||||
async def _resolve(
|
||||
session: AsyncSession, cred_id: str | None, type_: str
|
||||
) -> ProviderCredential | None:
|
||||
"""按 FK id 取凭证;id 为空或失效 → 回退该 type 默认。"""
|
||||
if cred_id:
|
||||
cred = await session.get(ProviderCredential, cred_id)
|
||||
if cred:
|
||||
return cred
|
||||
return await _default_credential(session, type_)
|
||||
def _value(resource: ModelResource | None, key: str, default):
|
||||
if not resource:
|
||||
return default
|
||||
value = (resource.values or {}).get(key, default)
|
||||
return default if value is None else value
|
||||
|
||||
|
||||
def _secret(resource: ModelResource | None, key: str, default: str) -> str:
|
||||
if not resource:
|
||||
return default
|
||||
return str((resource.secrets or {}).get(key) or default)
|
||||
|
||||
|
||||
async def resolve_runtime_config(
|
||||
session: AsyncSession, assistant_id: str
|
||||
) -> AssistantConfig:
|
||||
"""加载助手 + 解析凭证,产出可直接交给管线的运行时配置(含真 key)。"""
|
||||
"""加载助手 + 解析模型资源,产出可直接交给管线的运行时配置(含真 key)。"""
|
||||
assistant = await session.get(Assistant, assistant_id)
|
||||
if assistant is None:
|
||||
raise ValueError(f"助手不存在: {assistant_id}")
|
||||
|
||||
llm = await _resolve(session, assistant.llm_credential_id, "LLM")
|
||||
stt = await _resolve(session, assistant.asr_credential_id, "ASR")
|
||||
tts = await _resolve(session, assistant.tts_credential_id, "TTS")
|
||||
realtime = await _resolve(session, assistant.realtime_credential_id, "Realtime")
|
||||
llm_resource = await _resource_for(session, assistant.id, "LLM")
|
||||
stt_resource = await _resource_for(session, assistant.id, "ASR")
|
||||
tts_resource = await _resource_for(session, assistant.id, "TTS")
|
||||
realtime_resource = await _resource_for(session, assistant.id, "Realtime")
|
||||
|
||||
return AssistantConfig(
|
||||
name=assistant.name,
|
||||
@@ -55,21 +66,28 @@ async def resolve_runtime_config(
|
||||
prompt=assistant.prompt or "你是一个有帮助的助手。",
|
||||
runtimeMode=assistant.runtime_mode, # type: ignore[arg-type]
|
||||
enableInterrupt=assistant.enable_interrupt,
|
||||
# 模型/音色:凭证的模型ID优先
|
||||
model=(llm.model_id if llm else ""),
|
||||
asr=(stt.model_id if stt else ""),
|
||||
tts_model=(tts.model_id if tts else ""),
|
||||
voice=(tts.voice if tts else ""),
|
||||
stt_language=(stt.language if stt else ""),
|
||||
tts_speed=(tts.speed if tts else 1.0),
|
||||
stt_interface_type=(stt.interface_type if stt else "openai"),
|
||||
tts_interface_type=(tts.interface_type if tts else "openai"),
|
||||
realtimeModel=(realtime.model_id if realtime else ""),
|
||||
# 运行时连接信息(真 key + url):凭证优先,否则 .env 兜底
|
||||
llm_api_key=(llm.api_key if llm else config.LLM_API_KEY),
|
||||
llm_base_url=(llm.api_url if llm else config.LLM_BASE_URL),
|
||||
stt_api_key=(stt.api_key if stt else config.STT_API_KEY),
|
||||
stt_base_url=(stt.api_url if stt else config.STT_BASE_URL),
|
||||
tts_api_key=(tts.api_key if tts else config.TTS_API_KEY),
|
||||
tts_base_url=(tts.api_url if tts else config.TTS_BASE_URL),
|
||||
# 模型/音色:模型资源中的配置优先
|
||||
model=str(_value(llm_resource, "modelId", "")),
|
||||
asr=str(_value(stt_resource, "modelId", "")),
|
||||
tts_model=str(_value(tts_resource, "modelId", "")),
|
||||
voice=str(_value(tts_resource, "voice", "")),
|
||||
stt_language=str(_value(stt_resource, "language", "")),
|
||||
tts_speed=float(_value(tts_resource, "speed", 1.0)),
|
||||
llm_interface_type=(llm_resource.interface_type if llm_resource else "openai-llm"),
|
||||
stt_interface_type=(stt_resource.interface_type if stt_resource else "openai-asr"),
|
||||
tts_interface_type=(tts_resource.interface_type if tts_resource else "openai-tts"),
|
||||
realtimeModel=str(_value(realtime_resource, "modelId", "")),
|
||||
llm_values=(llm_resource.values or {}) if llm_resource else {},
|
||||
llm_secrets=(llm_resource.secrets or {}) if llm_resource else {},
|
||||
stt_values=(stt_resource.values or {}) if stt_resource else {},
|
||||
stt_secrets=(stt_resource.secrets or {}) if stt_resource else {},
|
||||
tts_values=(tts_resource.values or {}) if tts_resource else {},
|
||||
tts_secrets=(tts_resource.secrets or {}) if tts_resource else {},
|
||||
# 运行时连接信息(真 key + url):模型资源优先,否则 .env 兜底
|
||||
llm_api_key=_secret(llm_resource, "apiKey", config.LLM_API_KEY),
|
||||
llm_base_url=str(_value(llm_resource, "apiUrl", config.LLM_BASE_URL)),
|
||||
stt_api_key=_secret(stt_resource, "apiKey", config.STT_API_KEY),
|
||||
stt_base_url=str(_value(stt_resource, "apiUrl", config.STT_BASE_URL)),
|
||||
tts_api_key=_secret(tts_resource, "apiKey", config.TTS_API_KEY),
|
||||
tts_base_url=str(_value(tts_resource, "apiUrl", config.TTS_BASE_URL)),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user