9.7 KiB
模型提供商、协议适配与模型路由开发说明
更新日期:2026-09-04。阶段 E 实现记录。本地小模型的实际安装与多模态队列属于阶段 F;本阶段保留并测试可注入的本地后端接口。
1. 设置与凭据
设置 → 模型提供商 → 新增 Provider 提供可搜索的 logo 预设网格,包含 DeepSeek、Kimi、阿里云百炼、智谱 GLM、火山方舟、硅基流动、百度千帆、腾讯混元、MiniMax、阶跃星辰,以及 OpenAI Chat / Responses、Anthropic 和 Ollama。图标打包到前端,使用时不请求第三方图片服务;来源和许可见前端 assets/providers 目录。
预设返回 preset_id、logo_id、name、provider_type、base_url、requires_credential、description 和 capabilities。能力标签表示预设接入范围,不保证该账号的每个模型支持全部能力。厂商专用媒体协议、Coding Plan 和海外地域需要使用对应地址,不能仅凭厂商名称推断协议兼容。
预设和自定义服务都可以直接输入 API Key。每个新配置分配独立 Credential ID,避免同厂商多账号相互覆盖。明文只留在密码输入框和专用请求中,提交、失败、切换预设及关闭时清空;密钥不进入 Pinia、localStorage、Provider 配置响应或模型路由。
凭据继续使用独立的 PUT /api/credentials/{credential_id} 和 Fernet 开发存储。plugin.*、mcp.* 是保留命名空间。桌面端阶段仍需要把主密钥管理迁移到 Stronghold。保存密钥与保存 Provider 是两个请求,Provider 保存失败时可能留下未引用的加密凭据,可通过凭据删除接口清理。
Provider 配置和 Credential ID 写入 SQLite provider_configs,重启后恢复。Mock 为内置 Provider,不能编辑或删除。PATCH 已支持变更 provider_type 并重新创建 Adapter;Base URL 限制为不带用户信息、查询或 fragment 的 HTTP(S) 地址。
2. 协议适配
支持的协议是 OpenAI Chat Completions、OpenAI-Compatible、OpenAI Responses、Anthropic Messages 和 Ollama。Agent、Chat、Skill 仍只依赖内部 ModelRequest / ModelEvent / ProviderTurn,不直接解释厂商协议。
Adapter 负责消息及 Tool 历史转换、增量文本、可用的 reasoning delta、工具参数片段、usage、终止与统一错误。外部错误正文不原样返回;HTTP 鉴权、限流、超时、无效数据、流中断分别映射为内部错误。取消继续传播并关闭上游连接,不触发第二次本地推理。
GET /api/providers/{provider_id}/models 用于发现模型。模型列表不等于每个模型的能力承诺;部分厂商或代理不提供 /models 时,允许直接手动输入模型 ID。连接测试验证模型发现接口,不代表每一种媒体模型已完成真实推理验收。
3. 三类模型路由
接口:
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /api/model-routing |
读取配置和本地后端状态 |
| PUT | /api/model-routing |
带版本更新三类模型绑定 |
| POST | /api/models/embeddings |
文本向量,返回来源和回退原因 |
| POST | /api/media/transcriptions |
附件转写作业 |
| GET | /api/media/transcriptions/{job_id} |
获取转写作业 |
| POST | /api/media/speaker-matches |
两个音频附件的声纹相似度 |
设置 → 索引与模型分别选择 Embedding、音频转文本和声纹匹配。三种绑定互相独立,可使用不同提供商、模型、密钥和 API 路径。
GET / PUT 响应:
{
"config": {
"version": 1,
"embedding": {
"provider_id": "provider_example",
"model": "your-embedding-model",
"endpoint": "/embeddings",
"dimensions": null
},
"transcription": null,
"speaker_matching": null
},
"local_backends": [
{"capability": "embedding", "status": "placeholder", "message": "当前为 hash-v1 占位向量"},
{"capability": "transcription", "status": "not_installed", "message": "阶段 F 接入"},
{"capability": "speaker_matching", "status": "not_installed", "message": "阶段 F 接入"}
]
}
PUT body 只提交 config 的内容。version 为读取时的版本,成功递增;并发更新返回 MODEL_ROUTING_VERSION_CONFLICT。绑定为空表示使用本地后端。删除仍被路由引用的 Provider 返回 PROVIDER_IN_USE,须先解除绑定。
本阶段三类远程路由使用 openai_chat / openai_compatible 的 Bearer HTTP 配置,endpoint 只能是该提供商下的路径。Responses、Anthropic 和 Ollama 原生协议不冒充上述媒体协议;Ollama 用户需要另建兼容 HTTP 配置才能用于当前远程 Embedding 接口。
调用规则:无绑定 → 本地接口;有绑定 → API → 校验结果 → 失败或无效时调用本地接口。Provider 停用、密钥缺失、鉴权失败、限流、网络超时及无效结果均可回退;用户取消不会回退。附件不存在、大小非法等输入错误直接返回,不把用户输入错误当成模型故障。
4. Embedding 与索引一致性
请求使用 model、input、encoding_format: float;只有明确配置维度时才发送 dimensions。按最多 32 条分批请求,全部批次有效才使用 API 结果。校验返回数量、连续唯一 index、维度一致性、有限数值、非零范数,并 L2 归一化。维度可为 1–16384,不截断、补零或混用不同模型的向量。
返回 vectors、source、model_id、dimensions、fallback_reason。远程空间 ID 由完整 API URL、模型和实际维度生成;即使维度相同,不同模型的空间也不同。
笔记索引始终保留现有 hash/sqlite-vec 本地基线,远程向量写入独立 routed_block_vectors 表。远程查询只搜索对应空间,并要求覆盖全部当前 Block。API 失败、索引缺失、不完整或损坏时使用完整本地索引。切换模型、URL、维度后应在设置中重建全部索引。旧空间与当前文本不会混合打分,删除笔记或重建索引会通过外键清理远程向量。
当前远程侧索引采用 SQLite JSON 向量和精确余弦扫描,复杂度 O(Block 数量 × 维度),适用于当前小型 Vault;后续大规模索引需替换为按空间隔离的 ANN。网络等待发生在数据库写事务之前,当前仍会增加保存或重建延迟,异步索引队列尚未接入。
无 API 时使用的 HashEmbeddingProvider 是确定性特征哈希占位实现,不是已集成的小型语义模型。真实本地 Embedding 可实现既有 EmbeddingProvider 接口注入。
5. 音频与声纹边界
转写默认请求 /audio/transcriptions,multipart 字段 model、可选 language 和 file,响应必须包含非空字符串 text。已有纯文本附件和 Host 旁路 .txt 导入保留,来源标记 sidecar,不伪称 ASR。转写作业新增 source、fallback_reason;回退失败的作业记录 LOCAL_MODEL_NOT_INSTALLED 等明确错误。作业目前同步执行、限量保存在内存中,不是持久化异步队列。
声纹匹配使用本项目自定义 HTTP 契约,默认 /audio/speaker-matches,multipart 字段 model、file、reference_file;响应为 {"score": 0.85},score 必须为有限的 0–1 数值。公共入口只接受 attachment_id 和 reference_attachment_id,不接收任意文件路径。此接口用于一对一声纹比对,不等同于 pyannote 说话人分离,也不声称任意国内厂商原生支持该路径。
媒体文件限制 1 字节至 25 MiB,API 响应限制 16 MiB,单次请求超时 30 秒。文件从后端受控附件目录读取,使用结束或取消时关闭句柄。
LocalSpeechBackend 提供 transcribe 和 match 接口。阶段 E 默认 PendingSpeechBackend 明确报告未安装;阶段 F 接入 faster-whisper、pyannote.audio 及模型资源后替换。当前 diarization=true 明确返回失败作业 DIARIZATION_NOT_IMPLEMENTED,不会静默忽略。视频解码、TTS、视频生成及厂商专用异步媒体协议不在本次交付内。
6. 官方协议依据与验证
国内通用地址核对依据:阿里云百炼兼容接口、百度千帆兼容接口、腾讯混元兼容接口、MiniMax 文本接口、阶跃星辰通用与套餐地址区别、火山方舟 API、智谱开放接口。模型 ID 以账号实际开通列表为准,不写死“最新模型”。
流式事件依据:OpenAI Responses streaming、Anthropic streaming。音频请求依据:SiliconFlow transcription。
自动化验证使用虚构凭据、本地附件、httpx.MockTransport 和可注入本地模型,覆盖流式 Tool/Usage/取消、错误映射、回退、索引空间隔离、版本冲突、重启恢复和界面凭据行为。没有使用真实 API Key 或向厂商发送推理请求。最终验证:后端全量 410 项、前端 76 项测试通过,Vue/TypeScript 类型检查和生产构建通过,浅色/深色预设页面与路由保存经过浏览器检查,git diff --check 通过。后端仅保留既有 Starlette 测试客户端弃用提示,前端保留既有大 bundle 提示。
cd backend
uv run pytest -q -p no:cacheprovider
cd ../frontend
pnpm test
pnpm build