Implement knowledge base management and enhance assistant configuration

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.
This commit is contained in:
Xin Wang
2026-06-09 08:31:39 +08:00
parent 34fba494a3
commit b444ea777c
6 changed files with 304 additions and 56 deletions

View File

@@ -1,7 +1,7 @@
"""assistant_id → 运行时配置(把真 key 在服务端组装好)。
浏览器只传 assistant_id;真 key 在这里从 provider_credentials 取出注入。
取不到凭证记录时,降级用 .env 默认值(开发期零配置仍能跑)
助手按 FK(*_credential_id)引用凭证;取不到则回退该 type 默认凭证,再回退 .env
"""
import config
@@ -11,49 +11,57 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
async def _get_credential(
session: AsyncSession, type_: str, name: str = ""
async def _default_credential(
session: AsyncSession, type_: str
) -> ProviderCredential | None:
"""取某类(LLM/ASR/TTS)凭证:优先按资源名匹配,否则取该类默认"""
stmt = select(ProviderCredential).where(ProviderCredential.type == type_)
if name:
# 助手按资源名引用(如 model="DeepSeek-V3");命中则用它
named = (
await session.execute(stmt.where(ProviderCredential.name == name).limit(1))
).scalar_one_or_none()
if named:
return named
stmt = stmt.order_by(
ProviderCredential.is_default.desc(), ProviderCredential.id.asc()
).limit(1)
"""该 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)。
type 映射:LLM→大模型, ASR→语音识别, TTS→语音合成。
"""
"""加载助手 + 解析凭证,产出可直接交给管线的运行时配置(含真 key)。"""
assistant = await session.get(Assistant, assistant_id)
if assistant is None:
raise ValueError(f"助手不存在: {assistant_id}")
llm = await _get_credential(session, "LLM", assistant.model)
stt = await _get_credential(session, "ASR", assistant.asr)
tts = await _get_credential(session, "TTS")
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")
cfg = assistant.config or {}
return AssistantConfig(
name=assistant.name,
greeting=assistant.greeting,
prompt=assistant.prompt,
# 提示词/工作流类型把 prompt 放 config;外部类型由其平台编排,这里给个兜底
prompt=cfg.get("prompt") or "你是一个有帮助的助手。",
runtimeMode=assistant.runtime_mode, # type: ignore[arg-type]
enableInterrupt=assistant.enable_interrupt,
# 模型/音色:凭证的模型ID优先,否则助手里填的
model=(llm.model_id if llm else assistant.model),
asr=(stt.model_id if stt else assistant.asr),
voice=assistant.voice,
# 模型/音色:凭证的模型ID优先
model=(llm.model_id if llm else ""),
asr=(stt.model_id if stt else ""),
voice=cfg.get("voice", ""), # 音色不再是独立列,若 config 带则用,否则 .env 兜底
realtimeModel=(realtime.model_id if realtime else cfg.get("realtimeModel", "")),
# 运行时连接信息(真 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),