凭证池

凭证池允许你为同一提供商注册多个 API 密钥或 OAuth 令牌。当一个密钥达到速率限制或计费配额时,Hermes 会自动轮换到下一个健康密钥——让你的会话保持活动,无需切换提供商。

这与回退提供商不同,后者会切换到完全不同的提供商。凭证池是同一提供商内的轮换;回退提供商是跨提供商的故障转移。凭证池首先尝试——如果所有池密钥都耗尽,然后才会激活回退提供商。

工作原理

你的请求
  → 从池中选择密钥(round_robin / least_used / fill_first / random)
  → 发送给提供商
  → 429 速率限制?
      → 重试同一密钥一次(短暂波动)
      → 第二次 429 → 轮换到下一个池密钥
      → 所有密钥耗尽 → fallback_model(不同提供商)
  → 402 计费错误?
      → 立即轮换到下一个池密钥(24 小时冷却)
  → 401 认证过期?
      → 尝试刷新令牌(OAuth)
      → 刷新失败 → 轮换到下一个池密钥
  → 成功 → 正常继续

快速开始

如果你已经在 .env 中设置了 API 密钥,Hermes 会自动将其发现为 1 密钥池。要利用池化优势,添加更多密钥:

# 添加第二个 OpenRouter 密钥
hermes auth add openrouter --api-key sk-or-...-key
 
# 添加第二个 Anthropic 密钥
hermes auth add anthropic --type api-key --api-key sk-ant...-key
 
# 添加 Anthropic OAuth 凭证(需要 Claude Max 计划 + 额外使用积分)
hermes auth add anthropic --type oauth
# 打开浏览器进行 OAuth 登录

查看你的池:

hermes auth list

输出:

openrouter(2 个凭证):
  #1  OPENROUTER_API_KEY   api_key env:OPENROUTER_API_KEY ←
  #2  backup-key           api_key manual

anthropic(3 个凭证):
  #1  hermes_pkce          oauth   hermes_pkce ←
  #2  claude_code          oauth   claude_code
  #3  ANTHROPIC_API_KEY    api_key env:ANTHROPIC_API_KEY

标记当前选中的凭证。

交互式管理

运行 hermes auth(无子命令)进入交互式向导:

hermes auth

这会显示你的完整池状态并提供菜单:

你想做什么?
  1. 添加凭证
  2. 移除凭证
  3. 重置提供商冷却状态
  4. 设置提供商的轮换策略
  5. 退出

对于同时支持 API 密钥和 OAuth 的提供商(Anthropic、Nous、Codex),添加流程会询问类型:

anthropic 同时支持 API 密钥和 OAuth 登录。
  1. API 密钥(从提供商仪表盘粘贴密钥)
  2. OAuth 登录(通过浏览器认证)
输入 [1/2]:

CLI 命令

命令描述
hermes auth交互式池管理向导
hermes auth list显示所有池和凭证
hermes auth list <provider>显示特定提供商的池
hermes auth add <provider>添加凭证(提示输入类型和密钥)
hermes auth add <provider> --type api-key --api-key <key>非交互式添加 API 密钥
hermes auth add <provider> --type oauth通过浏览器登录添加 OAuth 凭证
hermes auth remove <provider> <index>按从 1 开始的索引移除凭证
hermes auth reset <provider>清除所有冷却/耗尽状态

轮换策略

通过 hermes auth → “设置轮换策略”或在 config.yaml 中配置:

credential_pool_strategies:
  openrouter: round_robin
  anthropic: least_used
策略行为
fill_first(默认)使用第一个健康密钥直至耗尽,然后移动到下一个
round_robin均匀循环使用密钥,每次选择后轮换
least_used始终选择请求计数最低的密钥
random在健康密钥中随机选择

错误恢复

凭证池对不同错误的处理方式不同:

错误行为冷却时间
429 速率限制重试同一密钥一次(短暂)。第二次连续 429 轮换到下一个密钥1 小时
402 计费/配额立即轮换到下一个密钥24 小时
401 认证过期首先尝试刷新 OAuth 令牌。仅当刷新失败时轮换
所有密钥耗尽如果配置了 fallback_model,则回退到它

has_retried_429 标志在每次成功 API 调用时重置,因此单次短暂 429 不会触发轮换。

自定义端点池

自定义 OpenAI 兼容端点(Together.ai、RunPod、本地服务器)拥有自己的池,以 custom_providers 中的端点名称为键(来自 config.yaml)。

当你通过 hermes model 设置自定义端点时,它会自动生成一个名称,如 “Together.ai” 或 “Local (localhost:8080)“。该名称成为池的键。

# 通过 hermes model 设置自定义端点后:
hermes auth list
# 显示:
#   Together.ai(1 个凭证):
#     #1  config key    api_key config:Together.ai ←
 
# 为同一端点添加第二个密钥:
hermes auth add Together.ai --api-key sk-tog...-key

自定义端点池存储在 auth.jsoncredential_pool 下,带有 custom: 前缀:

{
  "credential_pool": {
    "openrouter": [...],
    "custom:together.ai": [...]
  }
}

自动发现

Hermes 会自动从多个来源发现凭证,并在启动时填充到池中:

来源示例自动播种?
环境变量OPENROUTER_API_KEYANTHROPIC_API_KEY
OAuth 令牌(auth.json)Codex 设备码、Nous 设备码
Claude Code 凭证~/.claude/.credentials.json是(Anthropic)
Hermes PKCE OAuth~/.hermes/auth.json是(Anthropic)
自定义端点配置model.api_key 在 config.yaml 中是(自定义端点)
手动条目通过 hermes auth add 添加持久化在 auth.json 中

自动播种的条目在每次池加载时更新——如果你移除了环境变量,其池条目会自动修剪。手动条目(通过 hermes auth add 添加)永远不会自动修剪。

委托和子代理共享

当代理通过 delegate_task 生成子代理时,父级的凭证池会自动与子代理共享:

  • 相同提供商——子代理接收父级的完整池,支持密钥轮换以应对速率限制
  • 不同提供商——子代理加载该提供商自己的池(如果已配置)
  • 未配置池——子代理回退到继承的单个 API 密钥

这意味着子代理与父代理享有相同的速率限制弹性,无需额外配置。每任务凭证租赁确保子代理在并发轮换密钥时不会相互冲突。

线程安全

凭证池对所有状态变更使用线程锁(select()mark_exhausted_and_rotate()try_refresh_current()mark_used())。这确保了网关同时处理多个聊天会话时的安全并发访问。

架构

完整的数据流图请参见仓库中的 docs/credential-pool-flow.excalidraw

凭证池在提供商解析层集成:

  1. agent/credential_pool.py——池管理器:存储、选择、轮换、冷却
  2. hermes_cli/auth_commands.py——CLI 命令和交互式向导
  3. hermes_cli/runtime_provider.py——池感知的凭证解析
  4. run_agent.py——错误恢复:429/402/401 → 池轮换 → 回退

存储

池状态存储在 ~/.hermes/auth.jsoncredential_pool 键下:

{
  "version": 1,
  "credential_pool": {
    "openrouter": [
      {
        "id": "abc123",
        "label": "OPENROUTER_API_KEY",
        "auth_type": "api_key",
        "priority": 0,
        "source": "env:OPENROUTER_API_KEY",
        "access_token": "sk-or-v1-...",
        "last_status": "ok",
        "request_count": 142
      }
    ]
  },
}

策略存储在 config.yaml 中(而非 auth.json):

credential_pool_strategies:
  openrouter: round_robin
  anthropic: least_used