账号与算力池
在 AI Switch 里,"账号"是一条路由凭据(route credential)。每条凭据都归属某一个平台(Codex、Claude Code、Gemini CLI、Grok、OpenCode、OpenClaw、Hermes),被加入该平台的算力池之后,本地代理就可以在多条凭据之间自动挑选、轮换和退避。
本页说明凭据由哪些字段构成、状态如何流转、调度器按什么顺序选号,以及批量维护与导入导出时需要注意什么。
两种凭据来源
数据库对凭据类型做了硬约束,只有两种取值:
kind TEXT NOT NULL CHECK (kind IN ('official','api'))| 类型 | 来源 | 典型内容 |
|---|---|---|
official | 从官方登录态导入(粘贴文本或选择文件) | 官方 OAuth 凭据、订阅信息、配额窗口 |
api | 手工填写或导入的第三方 API 凭据 | Base URL + API Key + 上游协议方言 |
导入官方凭据时必须提供批次名称(batch name)。服务层会先创建一个 route_credential_import 来源的批次记录,再把导入结果挂到该批次上;批次名为空会直接返回 validation.batch_name_required。批次让"一次导入 30 个账号"之后还能按来源批量归档或改状态。
不同平台对凭据类型的支持程度不同:OpenCode、OpenClaw、Hermes 只接受 api 类型凭据,且必须显式指定 Base URL 和协议方言。详见 平台支持矩阵。
API 凭据的字段
创建 API 凭据时,敏感值与非敏感配置分开存放:API Key 落在 secret_payload_json,其余落在 config_json。
| 字段 | 存放位置 | 说明 |
|---|---|---|
| 显示名称 | display_name | 列表与日志里的标识,必填 |
| Base URL | config_json.base_url | 上游接口根地址,必填 |
| API Key | secret_payload_json.api_key | 必填;不会随普通列表接口返回明文 |
| 上游协议方言 | config_json.interface_format | openai / openai-responses / anthropic / gemini |
| 模型映射 | config_json.model_mappings | 数组,每项含 from、to,可选 label 与 supports_1m |
| 已拉取模型列表 | config_json.fetched_models | 由模型列表拉取写入,见 模型连通性测试 |
| 自定义工具兼容 | config_json.responses_custom_tool_compat | 布尔,默认 false |
| 自定义 User-Agent | config_json.headers["User-Agent"] | 可选;填写后作为固定请求头下发 |
| 每轮纠偏提醒 | config_json.turn_reminder | 布尔;开启后每轮在最后一条用户消息后追加一句要求。关闭时该键不写入 |
| 提醒内容 | config_json.turn_reminder_text | 可选;留空则用默认「请用简体中文回复。」 |
| 失败策略 | config_json.failure_policy | 单账号覆盖重试与语义失败阈值,见 稳定性与自动恢复 |
| 自动恢复规则 | config_json.recovery | 定时或探测恢复,见 稳定性与自动恢复 |
| 中转站余额查询 | config_json.relay_balance | 查询方式与自定义取值路径,见下节;关闭时该键不写入 |
| 最近一次余额 | config_json.relay_balance_snapshot | 查询结果快照,由「查余额」写入 |
Anthropic 方言的鉴权字段可选。 api_key_field 只接受两个值:ANTHROPIC_API_KEY(默认,走 x-api-key 请求头)或 ANTHROPIC_AUTH_TOKEN(走 Authorization: Bearer)。填其他值会被拒绝。这一项对应很多第三方 Claude 兼容网关只认其中一种头的现实情况。
协议方言的含义、以及它如何决定请求被改写成什么形状,见 协议路由与桥接。
新增与编辑面板的分区
字段多了以后,新增弹窗与编辑抽屉都按 tab 分区,切换 tab 不会丢已填内容:
| 分区 | 编辑抽屉 | 新增弹窗(API 账号模式) |
|---|---|---|
| 基础 | 名称、邮箱(官方账号)、状态、API Key、Base URL、接口格式、Claude 鉴权字段、模型映射 | 账号预设、名称、API Key、Base URL、接口格式、Claude 鉴权字段、模型映射 |
| 高级 | 路由优先级、最大并发数、User-Agent、custom 工具兼容、内联远程图片、每轮纠偏提醒、中转站余额查询 | User-Agent、custom 工具兼容、中转站余额查询、预览 JSON |
| 故障处理 | 失败处理策略、模型状态、自动恢复 | 无(这些字段只在创建之后才有) |
| 其他 | Secret JSON、Config JSON(官方账号)、Preview JSON | 无 |
新增弹窗只有「API 账号」模式分区,批量导入和导入其他客户端两个模式字段少,保持原样。保存校验失败时面板会自动跳到出错字段所在的分区,不会只在底部留一条看不出位置的报错。
中转站余额查询
中转站账号(kind = "api")的钱会用完。不查的话只能等它在一次真实请求里报「用户额度不足」被置为异常;主动查一下,就能提前看到哪个号快没钱了。
「高级」分区的分段器选查询方式,四档:
| 档位 | 请求 | 要额外填什么 |
|---|---|---|
| 关闭 | 不查 | — |
| new-api | GET <面板根>/api/usage/token/ | 什么都不用填 |
| sub2api | GET <面板根>/v1/usage | 什么都不用填 |
| 自定义 | 你指定的 URL | 请求 URL、剩余额度的取值路径,其余可选 |
两个内置档都只用账号已经有的 Base URL 和 API Key,不需要另取面板的访问令牌或用户 ID。
面板根是自动推导的。 账号的 Base URL 通常是 https://panel.example.com/v1,而面板接口挂在根上。查询时会先剥掉尾部的 /v1、/v1beta、/openai/v1 得到面板根,再按「面板根 → 原始 Base URL」的顺序依次试,命中即止,所以不用再单独填一遍面板地址。
new-api 的额度换算取自面板。 面板返回的是整数额度,要除以 QuotaPerUnit 才是美元。这个值管理员能改,所以查询前会先无鉴权读一次 GET <面板根>/api/status 的 quota_per_unit,读不到才退回默认的 500000。徽标的悬浮提示里会写明这次用的是哪个除数。
自定义档用点号路径取值,不执行任何脚本。
| 字段 | 说明 |
|---|---|
| 请求 URL | 必填,http:// 或 https:// 开头;鉴权沿用账号自己的 API Key |
| 剩余额度路径 | 必填,如 data.total_available;数字段可索引数组,如 data.plans.0.remaining |
| 已用 / 总额度 / 套餐名路径 | 可选 |
| 单位 | 可选,留空按 USD |
| 换算除数 | 可选,留空按 1 |
怎么查、在哪看
- 账号行的「查余额」按钮查单个账号;刷新菜单里的「查中转站余额」查本平台所有开启了查询的中转站账号。
- 查到的余额显示成账号行上的徽标(
余额 $37.70),悬浮可看套餐、已用、总额、到期、订阅与限流窗口,以及更新时间。余额 ≤ 0 时徽标变红,面板标记为不限额度时显示「余额 不限」。 - 编辑抽屉的分段器下方也有一枚徽标和「立即查询」,改配置后可以当场验证。
不会自动轮询,也不会因为余额低改账号状态。 new-api 的这个接口有每 key 20 次 / 20 分钟的限流,而这里的对象是一整池账号,自动轮询很容易把每个号都撞上限流;查询失败或面板改字段也不该连带把账号踢出调度。真的额度不足时,转发失败的那条路径仍然会把账号置为异常,见 稳定性与自动恢复。
账号状态机
状态字段只允许五种取值(校验函数 validate_route_credential_status):
| 状态 | 含义 | 是否参与调度 | 能否自动恢复 |
|---|---|---|---|
ok | 健康 | 是 | — |
warning | 观察中,出现过问题 | 否 | 能 |
error | 已判定不可用 | 否 | 能 |
paused | 手动暂停 | 否 | 能(需显式触发) |
revoked | 凭据已失效/被吊销 | 否 | 不能 |
数据库默认值是 ok。状态迁移由三类事件驱动:
- 请求成功:清空瞬时失败计数与退避窗口;如果当前是
error或warning,会被拉回ok。 - 请求失败:按失败类型分流。永久性失败(如 refresh token 已被吊销)直接写
revoked;可重试失败累加瞬时失败计数并设置退避窗口;语义失败按指纹累加连击计数,达到阈值才翻成error。 - 显式测试成功:对单个账号手动跑一次模型连通性测试并通过,会执行"显式测试恢复",清空所有失败计数与退避窗口。
revoked 是唯一的终态:重新激活的 SQL 带 WHERE id = ? AND status != 'revoked',自动恢复调度器也会跳过它。要让被吊销的账号复活,只能重新导入或改凭据内容。
paused 的账号仍然可以被显式测试。 这是有意设计:手动测一次,正是用户判断暂停中的账号是否已经恢复的方式。
账号状态之外还有一层模型状态。 同一个账号上的每个模型有自己的 ok / error / paused 三态与冷却窗口,存在 route_credential_models 表里。账号级 paused 让整个号退出调度,模型级 paused 只让这一个模型退出;两者互不影响。编辑抽屉「故障处理」分区的「模型状态」区块可以逐模型暂停、恢复或解除冷却。
失败分类的完整规则、退避时长与阈值,见 稳定性与自动恢复。
优先级与并发上限
两个调度参数在数据库层就带了约束:
ALTER TABLE route_credentials
ADD COLUMN route_priority INTEGER NOT NULL DEFAULT 3
CHECK (route_priority BETWEEN 1 AND 5);
ALTER TABLE route_credentials
ADD COLUMN max_concurrency INTEGER NOT NULL DEFAULT 1
CHECK (max_concurrency >= 1);建表时的列默认值仍是 1,但创建账号的写入路径会显式绑定 5(DEFAULT_ROUTE_CREDENTIAL_MAX_CONCURRENCY),所以新账号的实际默认并发是 5。已存在的账号不受影响。
| 参数 | 范围 | 默认 | 作用 |
|---|---|---|---|
route_priority | 1–5 | 3 | 数值越小越优先。同一数值的账号构成一个优先级组 |
max_concurrency | ≥ 1 | 5 | 该账号同时在飞的请求上限;占满则本轮跳过它 |
调度顺序
一次代理请求的选号过程是:
- 筛出候选。SQL 条件要求:在池内且启用(
route_pool_members.enabled = 1)、未归档(archived_at IS NULL)、状态为ok,且配额字段(primary_remain、weekly_remain)为空或大于 0。这一步不判冷却。 - 排序。
ORDER BY route_priority ASC, sort_order ASC, created_at ASC—— 先按优先级,再按池内手工排序,最后按创建时间。 - 分组轮转。候选按
route_priority分组,组内从持久化游标(route_pool_cursors表按平台记录next_index)开始轮询。游标持久化意味着重启应用之后不会每次都从第一个账号打起。 - 按模型过滤并解析模型键。按平台能力规则与请求里的模型名筛一遍,同一趟为每个留下的候选算出它该被记账的上游模型键(
model_mappings的to;官方账号取请求原名)。筛空返回route_pool.model_unmatched。 - 查模型状态并剔除冷却。按
(账号 ID, 模型键)对批量读route_credential_models,然后:被暂停或判为异常的模型硬排除,不参与任何兜底;账号级或模型级冷却未过期的进冷却桶。如果这样一来一个可用账号都不剩,调度器只保留最快恢复的那一个冷却账号去试,而不是让请求直接失败;如果连兜底都没有(全被硬排除),返回route_pool.model_unavailable。 - 取并发租约。逐个尝试
try_acquire(platform, id, max_concurrency);拿不到租约就顺延到重试队列里的下一个账号。
第 4 步必须在第 5 步之前。 该不该判为冷却取决于请求哪个模型,不知道模型就无法分桶。完整规则见 稳定性与自动恢复。
支撑这套查询的复合索引是:
CREATE INDEX IF NOT EXISTS idx_route_credentials_routing_priority
ON route_credentials(platform, route_priority, status, next_retry_at, cooldown_until);索引列顺序也正是调度语义:池按平台切分,组内按优先级排序,冷却中的账号被排除在外。 模型级状态另有一个索引:
CREATE INDEX IF NOT EXISTS idx_route_credential_models_lookup
ON route_credential_models(route_credential_id, status, cooldown_until);怎么配这两个值
- 主力号 + 备用号:主力设 1 或 2,备用设 4 或 5。只有主力全部冷却或占满时,流量才会落到备用号。
- 同权重摊平:所有号设同一优先级,靠组内轮转均摊,配额消耗更平均。
- 并发上限对齐上游限制:上游按 key 限制并发时,把
max_concurrency设成它允许的值。新账号默认 5;对并发敏感的上游改成 1,就退回到同一个账号任意时刻只有一个在飞请求。
列表视图与批量操作
账号列表按三个互斥的范围(RouteCredentialPoolScope)分页展示:
| 范围 | 含义 |
|---|---|
in_pool | 已加入该平台算力池 |
out_of_pool | 已存在但未入池(默认范围) |
archived | 已归档 |
分页大小只接受 20、50、100 三个值,其他值会被 page_size must be 20, 50, or 100 拒绝。
可用的批量与单条操作:
| 操作 | 行为 |
|---|---|
| 归档 / 恢复归档 | 批量写入或清空 archived_at;归档账号不参与任何调度 |
| 批量设置状态 | 对选中 ID 统一写入某个合法状态 |
| 拖拽排序 | 按"移动到某两个账号之间"重算 sort_order,在当前筛选与范围内生效 |
| 复制 | 复制一条凭据,新名称是原名加上 YYYY-MM-DD 日期戳 |
| 删除 | 物理删除;池成员表带 ON DELETE CASCADE,成员记录同步消失 |
归档 vs 删除:归档是可逆的软隐藏,凭据与历史统计都还在;删除不可逆。定期轮换一批号时,归档比删除更合适。
归档专用索引同样是复合的:
CREATE INDEX IF NOT EXISTS idx_route_credentials_archive
ON route_credentials(platform, archived_at, sort_order);导出与导入
导出对话框提供两种格式:JSON 文件和方案链接(scheme link)。
导出即泄露风险
此导出内容包含凭据。请妥善保管,并删除不再需要的副本。
JSON 导出
- 建议文件名形如
ai-switch-<platform>-route-credentials-20260819-101530.json,平台与 UTC 时间戳都写在名字里。 - 载荷带
schema_version: 1,以及来源实例 ID、来源凭据 ID、平台、类型等元数据;可以关闭"增强元数据"只导出核心字段。 - 单次导出最多 2000 条凭据,序列化后最大 8 MiB(
8 * 1024 * 1024)。超限返回transfer.selection_too_large或transfer.export_too_large。 - 桌面端走系统保存对话框;Web 服务模式下走浏览器下载。
方案链接
方案链接是形如 aiswitch://v1/import?... 的深链,只对 api 类型凭据生成(官方凭据没有可放进 URL 的等价形式)。
复制方案链接
复制方案链接会将 API 密钥放入系统剪贴板。
点击复制按钮时会先弹确认框,文案是:
此方案链接包含 API 密钥。是否复制到系统剪贴板?
确认之后才会真正写入剪贴板。关闭导出对话框会立即清掉界面上持有的敏感状态。
导入去重
导入端会记录每条凭据的来源身份,避免同一份导出被反复导入成重复账号:
CREATE TABLE IF NOT EXISTS route_credential_transfer_origins (
route_credential_id TEXT PRIMARY KEY,
source_instance_id TEXT NOT NULL,
source_credential_id TEXT NOT NULL,
source_platform TEXT NOT NULL,
source_kind TEXT NOT NULL,
source_schema_version INTEGER NOT NULL,
source_fingerprint TEXT NOT NULL,
imported_at TEXT NOT NULL,
UNIQUE(source_instance_id, source_credential_id, source_platform)
);每个安装实例自己也有一个稳定身份(transfer_installation_identity),所以"从 A 机器导出、导入到 B 机器"和"在同一台机器上重复导入"是可区分的。
除自身格式之外,导入还兼容其他账号切换工具的导出格式(兼容导入协议)。schema_version 不匹配时返回 transfer.schema_version_unsupported,不会尝试猜测字段含义。
从其他客户端导入
新增账号对话框的第三个标签「导入其他客户端」直接读取本机另一个切换工具的配置文件,不需要先在那边导出。当前支持 CC Switch:
- 读取
~/.cc-switch/cc-switch.db(providers表)或旧版~/.cc-switch/config.json;设过CC_SWITCH_HOME时以该目录为准,也可以在对话框里手动选择文件。 - 数据库以只读 + immutable 方式打开,所以对方正在运行也能读,且不会在别人的目录里留下
-wal文件。 - 只读取 API 类账号:官方登录条目(
category = official)和 Claude 桌面版条目会被标为不可导入,并给出原因。 - 每条条目都会显示 Base URL、掩码后的 API Key、接口方言与模型映射条数,勾选后再导入。API Key 明文不会经过前端。
- 属于其他平台的条目只计数不列出——账号页是按平台分的,切到对应平台再导入即可。
字段映射按各客户端的原生配置来,方向与写入时相反:CC Switch 把上游模型名直接写在客户端的环境变量里,AI Switch 存的是「别名 → 上游模型」,所以每个环境变量键会还原成对应角色的映射。
| 来源 | 取值 | 落到 AI Switch |
|---|---|---|
env.ANTHROPIC_AUTH_TOKEN / env.ANTHROPIC_API_KEY | API Key | api_key,并据此决定 api_key_field(Bearer 还是 x-api-key) |
env.ANTHROPIC_BASE_URL | Base URL | base_url |
env.ANTHROPIC_DEFAULT_*_MODEL | 各角色上游模型 | claude-*-alias 映射,[1M] 后缀转成 supports_1m |
env.CLAUDE_CODE_SUBAGENT_MODEL / env.ANTHROPIC_MODEL | 子代理与兜底模型 | claude-subagent / claude-model 映射 |
auth.OPENAI_API_KEY | Codex API Key | api_key |
config 里选中的 [model_providers.*].base_url | Codex Base URL | base_url |
meta.apiFormat,回退到 wire_api | 接口方言 | interface_format |
meta.customUserAgent | 自定义 UA | config_json.headers["User-Agent"] |
重复导入即覆盖
去重键是 (external_source_client, external_source_id),其中 source id 是对方的记录主键(按 app 类型加了前缀,因为 CC Switch 的主键是 (id, app_type) 组合):
ALTER TABLE route_credentials ADD COLUMN external_source_client TEXT;
ALTER TABLE route_credentials ADD COLUMN external_source_id TEXT;
CREATE UNIQUE INDEX idx_route_credentials_external_source
ON route_credentials(external_source_client, external_source_id)
WHERE external_source_client IS NOT NULL AND external_source_id IS NOT NULL;再导入同一条记录时会覆盖上次导入的账号,而不是新增重复项,预览里也会直接标出「覆盖已有」和将被覆盖的账号名。这与前面的凭据传输导入不同:那里遇到被改过的重复项会拒绝导入,而这里外部客户端是它自己那些字段的权威来源。
覆盖只替换对方拥有的内容(名称、密钥、config_json、预览)并清空失败计数;本地改过的优先级、并发上限、批次和算力池成员关系都保留。revoked 依旧是终态——被吊销的账号不会因为重新导入而复活。「创建后加入算力池」也只作用于本次新增的账号。
存储位置
所有凭据数据都在应用的 SQLite 数据库里:数据目录固定为用户主目录下的 ~/.ai-switch,数据库文件名为 ai-switch.db(开发构建用独立的 ai-switch-dev.db,互不干扰)。
数据目录就是凭据目录
API Key 与官方登录凭据都存放在这个 SQLite 数据库里(route_credentials.secret_payload_json 列),没有额外的静态加密。请把整个 ~/.ai-switch 目录当作凭据目录对待:
- 不要把它放进公开仓库、未加密的同步盘或共享目录
- 备份时注意备份介质本身的安全性,并收紧目录的文件权限
- 建议在开启全盘加密的磁盘上使用
表结构由 src-tauri/migrations 下的 25 个只进式迁移脚本定义。与本页相关的主要迁移:
| 迁移 | 内容 |
|---|---|
202607130011_route_credentials.sql | route_credentials 主表、route_pool_members 池成员表 |
202607300001_route_credential_retry.sql | 瞬时失败计数、next_retry_at、cooldown_until |
202608040001_route_credential_transfer.sql | 安装实例身份与导入来源表 |
202608050001_route_credential_archive.sql | archived_at 与归档索引 |
202608060002_route_usage_breakdown.sql | usage_events 的 token 与价格拆分列 |
202608080002_route_credential_priority_concurrency.sql | route_priority、max_concurrency 与调度索引 |
202608130001_route_credential_semantic_failure_streak.sql | 语义失败连击计数与指纹 |
202609020001_route_credential_external_source.sql | 外部客户端来源列与去重唯一索引 |