这是什么
ai-profile 是一个 Rust crate,为桌面应用提供「让用户配置 AI 模型服务」所需的那一层: 服务商预置清单、跨应用配置互通协议、端点拼接、连通性验证与结构化错误。
它不是 LLM SDK —— 它不替你发起对话请求,不做流式解析,不管上下文。 它管的是在用户点下「开始对话」之前的所有事:选哪家、地址怎么拼、密钥对不对、模型名存不存在。
解决的问题
假设你有四个桌面应用,每个都要一个「模型服务」设置页。每个页面都需要:
- 一份服务商下拉(DeepSeek、智谱、通义、Kimi、OpenAI、Claude……)
- 每家的
base_url预填值 —— 而各家的版本段并不统一 - 每家的候选模型 id —— 而这些 id 每月都在变
- 一个「测试连接」按钮,以及失败时说得清楚的提示
于是同一套逻辑被写了四遍。问题不在于重复本身,而在于它们会各自漂移:
DeepSeek 在 2026-07-24 下线了
deepseek-chat/deepseek-reasoner两个老别名 (V4 发布时公告的三个月过渡期到期)。四个应用里只有一个及时改了预置, 另外三个的用户打开下拉、选中默认值、点击测试 —— 报错。 这类问题没有任何编译期信号,只能等用户报障。
把这部分收进一个库,漏更新至少变成全漏而不是「有的应用对、有的错」, 而修一次就全部生效。
边界:什么进、什么不进
| 内容 | 归属 | |
|---|---|---|
| ✅ | 预置清单(base_url / 模型 id / 协议 / 专有字段 / 申请页) | 本 crate |
| ✅ | ai.profile 跨应用配置交换协议 | 本 crate |
| ✅ | 端点拼接、模型清单清洗 | 本 crate |
| ✅ | 连通性验证与结构化错误 | 本 crate |
| ❌ | 密钥存储与加密 | 留给应用 |
| ❌ | 数据库、CRUD、激活态管理 | 留给应用 |
| ❌ | 对话 / 生图 / 语音的实际调用 | 留给应用或专门的 SDK |
为什么密钥存储不进来
四个应用的密钥存储方式差异极大:有的落系统密钥环,有的进 SQLCipher 加密金库, 有的就放在用户数据目录的明文配置里(本地自建服务的场景)。
这些差异不是历史包袱,而是各自合理的取舍 —— 强行统一只会让每个应用都得绕过它。 所以本 crate 一行存储代码都没有:验证时你把密钥传进来,用完即弃。
能力(kind)
| kind | 状态 | 执行模型 |
|---|---|---|
chat | 第一版可用 | 同步 / 流式 |
image | 规划中 | 同步 或 提交+轮询 |
video | 规划中 | 必然提交+轮询(各家轮询协议不同) |
tts | 规划中 | 同步,返回字节流 |
按 feature 开启,不用的不编进二进制 —— 只做对话的应用不会为生图能力付出任何体积。 详见安装与 feature。
三条设计约定
这三条贯穿全库,理解它们能省下很多「为什么是这样」的疑问。
1. base_url 原样使用,不推断版本段
各家的版本段并不统一:多数是 /v1,智谱是 /v4,Gemini 是 /v1beta/openai(还不在末尾)。
自动补全需要引入转义机制才能表达例外,而推断错的代价是隐性的: 用户照着官方文档填对了地址,库悄悄给他加了一段,他只看到 404, 而且会先怀疑是自己填错了。
所以本 crate 把 base_url 原样当作前缀,只负责拼上端点后缀。 详见端点与模型清单。
2. 默认模型选「够用档」而非「最强档」
用户点开预置是奔着"能用"来的,不是奔着"最贵"。把旗舰型号塞进默认值, 等于替他做了一个他没同意的花钱决定。最强档留在候选清单里随时可选。
3. 错误必须结构化
Err(String) 会让调用方只能显示一行红字。而 NotFound { suggested_url } 才能让界面给出「一键改用这个地址」的按钮。
六个错误变体各自对应一个具体的界面动作,详见错误码对照。
下一步
- 安装与 feature —— 把它加进你的项目
- 快速开始 —— 十几行代码跑通验证
- Tauri 应用接入 —— 完整的前后端接入示例