- Update Makefile to include new database seed commands for assistants and credentials. - Refactor assistant model to use explicit fields instead of a config dictionary, improving data integrity and clarity. - Implement new seeding SQL script for assistants, ensuring dependencies on credentials are respected. - Modify backend routes and frontend components to accommodate the new assistant structure, including direct field access for prompt, API URL, and keys. - Enhance the AssistantPage component to handle the new data structure and streamline the save process for different assistant types.
71 lines
3.0 KiB
Python
71 lines
3.0 KiB
Python
"""assistant_id → 运行时配置(把真 key 在服务端组装好)。
|
|
|
|
浏览器只传 assistant_id;真 key 在这里从 provider_credentials 取出注入。
|
|
助手按 FK(*_credential_id)引用凭证;取不到则回退该 type 默认凭证,再回退 .env。
|
|
"""
|
|
|
|
import config
|
|
from db.models import Assistant, ProviderCredential
|
|
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 _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_)
|
|
|
|
|
|
async def resolve_runtime_config(
|
|
session: AsyncSession, assistant_id: str
|
|
) -> AssistantConfig:
|
|
"""加载助手 + 解析凭证,产出可直接交给管线的运行时配置(含真 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")
|
|
|
|
return AssistantConfig(
|
|
name=assistant.name,
|
|
greeting=assistant.greeting,
|
|
# prompt 现在是真列;外部类型由其平台编排,这里给个兜底
|
|
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 ""),
|
|
voice="", # 音色无独立列,留空 → service_factory 回退 .env TTS_VOICE
|
|
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),
|
|
)
|