108 lines
5.1 KiB
Markdown
108 lines
5.1 KiB
Markdown
# 模型提供商与模型发现开发说明
|
||
|
||
> 更新日期:2026-08-30。OpenAI、DeepSeek、Ollama 预设、模型自动发现、默认模型选择和开发阶段加密凭据存储均已实现并接入设置页。
|
||
|
||
## 1. 本次目标
|
||
|
||
本次完善设置页的模型提供商配置,不改变 Agent、Chat 和 Skill 对统一 Model Core 接口的依赖:
|
||
|
||
- 提供 OpenAI、DeepSeek 和 Ollama 配置预设;
|
||
- 保存 Provider 后自动获取该账号或服务当前可用的模型列表;
|
||
- 支持手动刷新模型列表和选择默认模型;
|
||
- 保留自定义 OpenAI-Compatible 服务入口;
|
||
- 不在 Vue、FastAPI 配置或仓库文件中保存、回显 API Key 明文。
|
||
|
||
## 2. 接口与实现
|
||
|
||
### 2.1 Provider 预设
|
||
|
||
新增接口:
|
||
|
||
```http
|
||
GET /api/providers/presets
|
||
```
|
||
|
||
预设由后端 `ProviderFactory` 提供,前端只消费名称、协议类型、Base URL 和是否需要凭据等配置元数据,不直接实现厂商协议。
|
||
|
||
当前预设:
|
||
|
||
| 提供商 | Provider Type | Base URL | 默认 Credential ID |
|
||
| --- | --- | --- | --- |
|
||
| OpenAI | `openai_chat` | `https://api.openai.com/v1` | `openai` |
|
||
| DeepSeek | `openai_compatible` | `https://api.deepseek.com` | `deepseek` |
|
||
| Ollama | `ollama` | `http://127.0.0.1:11434` | 无 |
|
||
|
||
OpenAI 和 DeepSeek 都通过项目已有的 `OpenAICompatibleProvider` 访问。模型发现分别请求 Base URL 下的 `/models`,不引入厂商 SDK。
|
||
|
||
### 2.2 自动获取模型
|
||
|
||
模型列表继续使用既有接口:
|
||
|
||
```http
|
||
GET /api/providers/{provider_id}/models
|
||
```
|
||
|
||
设置页在以下时机调用该接口:
|
||
|
||
- Provider 列表加载完成后,为所有已启用 Provider 自动刷新;
|
||
- 新增或编辑 Provider 保存成功后自动刷新;
|
||
- 用户点击“刷新模型”时手动刷新;
|
||
- 打开已有 Provider 的编辑窗口时刷新可选模型。
|
||
|
||
前端按模型名称排序并按 `model_id` 去重。获取结果保存在 `providerStore.modelsByProvider`,加载状态和错误按 Provider 隔离,单个外部服务失败不会阻止其他服务展示。
|
||
|
||
获取成功后,Provider 卡片展示模型数量和默认模型下拉框。更换默认模型会调用 Provider PATCH 接口写回配置;编辑窗口仍允许手动输入模型 ID,以兼容未出现在列表中的代理模型或部署别名。
|
||
|
||
### 2.3 错误处理
|
||
|
||
Provider Adapter 的错误在 FastAPI 路由转换为统一 API Error:
|
||
|
||
| Provider Error | HTTP 状态 |
|
||
| --- | --- |
|
||
| `PROVIDER_AUTH_FAILED` | 401 |
|
||
| `MODEL_NOT_FOUND` | 404 |
|
||
| `PROVIDER_RATE_LIMITED` | 429 |
|
||
| `PROVIDER_TIMEOUT` | 504 |
|
||
| 其他 Provider 可用性错误 | 502 |
|
||
|
||
前端在对应 Provider 卡片内展示失败原因,并允许用户修正 Credential ID、Base URL 后重新获取。
|
||
|
||
## 3. 凭据边界
|
||
|
||
设置页选择 OpenAI 或 DeepSeek 预设后展示密码类型的 API Key 输入框,不再要求用户理解 Credential ID。输入值只存在于表单的临时 `ref`,不会写入 Pinia 或 localStorage;请求完成、取消表单或失败后都会清空。
|
||
|
||
API Key 通过独立接口写入:
|
||
|
||
```http
|
||
GET /api/credentials/{credential_id}
|
||
PUT /api/credentials/{credential_id}
|
||
DELETE /api/credentials/{credential_id}
|
||
```
|
||
|
||
PUT 请求使用 Pydantic `SecretStr` 接收密钥,响应仅包含 Credential ID 和 `configured` 状态。后端使用 Fernet 认证加密,将密文保存到 `data/credentials/credentials.json`,主密钥保存到 `data/credentials/master.key`;目录和文件尽可能设置为仅当前用户可访问并整体排除版本控制。写入采用临时文件替换,避免进程中断留下半写文件。Provider 发起请求时按 Credential ID 解密,解密失败转换为统一 Provider Error,任何读取接口均不返回明文。
|
||
|
||
本地开发存储的主密钥与密文仍位于同一用户数据目录,因此它解决的是仓库泄漏、普通配置误提交和静态明文暴露,不等同于操作系统安全硬件或 Stronghold。Tauri 集成后应以 Stronghold 实现替换 `EncryptedCredentialStore`。无界面环境仍兼容 `OPENAI_API_KEY`、`DEEPSEEK_API_KEY` 和 Host 注入的 `AINOTE_CREDENTIAL_<ID>`;设置页保存的本地密钥优先,环境变量仅作为回退。
|
||
|
||
自动化测试仅使用虚构测试值,验证磁盘文件不包含明文、加解密往返、API 响应不泄密,以及 Provider 能用解密后的值构造 Authorization Header。本次没有使用真实 OpenAI 或 DeepSeek Key,也没有向厂商发起真实请求。
|
||
|
||
## 4. 验证
|
||
|
||
后端:
|
||
|
||
```bash
|
||
cd backend
|
||
uv run pytest -q -p no:cacheprovider
|
||
```
|
||
|
||
前端:
|
||
|
||
```bash
|
||
cd frontend
|
||
pnpm test
|
||
pnpm build
|
||
```
|
||
|
||
自动化验证覆盖 Provider 预设、OpenAI-Compatible `/models` 请求与鉴权头、模型映射、前端自动刷新、排序去重及按 Provider 隔离错误。生产构建同时执行 Vue 和 TypeScript 类型检查。
|
||
|
||
当前完整回归基线:后端 87 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。
|