Hermes 拥有一个共享的 provider 运行时解析器,用于以下场景:
- CLI
- gateway
- cron 任务
- ACP
- 辅助模型调用
主要实现:
hermes_cli/runtime_provider.py--- 凭据解析,_resolve_custom_runtime()hermes_cli/auth.py--- provider 注册表,resolve_provider()hermes_cli/model_switch.py--- 共享/model切换流水线(CLI + gateway)agent/auxiliary_client.py--- 辅助模型路由providers/--- ABC + 注册表入口点(ProviderProfile、register_provider、get_provider_profile、list_providers)plugins/model-providers/<name>/--- 每个 provider 的插件(内置),声明api_mode、base_url、env_vars、fallback_models并在首次访问时将自身注册到注册表。用户插件位于$HERMES_HOME/plugins/model-providers/<name>/,会覆盖同名的内置插件。
providers/ 中的 get_provider_profile() 为给定 provider id 返回一个 ProviderProfile。runtime_provider.py 在解析时调用它,以获取规范的 base_url、env_vars 优先级列表、api_mode 和 fallback_models,无需在多个文件中重复这些数据。在 plugins/model-providers/<your-provider>/(或 $HERMES_HOME/plugins/model-providers/<your-provider>/)下添加一个调用 register_provider() 的新插件,即可让 runtime_provider.py 自动识别它------无需在解析器本身中添加分支。
如果你想添加一个新的一等推理 provider,请结合本页阅读 添加 Provider 和 Model Provider 插件指南。
解析优先级
从高层来看,provider 解析使用以下顺序:
- 显式 CLI/运行时请求
config.yaml中的模型/provider 配置- 环境变量
- provider 特定的默认值或自动解析
该顺序很重要,因为 Hermes 将已保存的模型/provider 选择视为正常运行的真实来源。这可以防止过时的 shell 导出变量悄悄覆盖用户在 hermes model 中最后选择的端点。
Provider
当前 provider 系列包括(完整内置集合见 plugins/model-providers/):
- OpenRouter
- Nous Portal
- OpenAI Codex
- Copilot / Copilot ACP
- Anthropic(原生)
- Google / Gemini(
gemini、google-gemini-cli) - Alibaba / DashScope(
alibaba、alibaba-coding-plan) - DeepSeek
- Z.AI
- Kimi / Moonshot(
kimi-coding、kimi-coding-cn) - MiniMax(
minimax、minimax-cn、minimax-oauth) - Kilo Code
- Hugging Face
- OpenCode Zen / OpenCode Go
- AWS Bedrock
- Azure Foundry
- NVIDIA NIM
- xAI(Grok)
- Arcee
- GMI Cloud
- StepFun
- Qwen OAuth
- Xiaomi
- Ollama Cloud
- LM Studio
- Tencent TokenHub
- Custom(
provider: custom)--- 适用于任何 OpenAI 兼容端点的一等 provider - 命名自定义 provider(
config.yaml中的custom_providers列表)
运行时解析的输出
运行时解析器返回的数据包括:
providerapi_modebase_urlapi_keysource- provider 特定的元数据,如过期/刷新信息
为什么这很重要
该解析器是 Hermes 能够在以下场景之间共享认证/运行时逻辑的主要原因:
hermes chat- gateway 消息处理
- 在全新会话中运行的 cron 任务
- ACP 编辑器会话
- 辅助模型任务
OpenRouter 与自定义 OpenAI 兼容 base URL
Hermes 包含相关逻辑,以避免在存在多个 provider 密钥时(例如同时存在 OPENROUTER_API_KEY 和 OPENAI_API_KEY)将错误的 API key 泄露给自定义端点。
每个 provider 的 API key 仅作用于其自身的 base URL:
OPENROUTER_API_KEY仅发送至openrouter.ai端点OPENAI_API_KEY用于自定义端点及作为回退
Hermes 还区分以下两种情况:
- 用户主动选择的真实自定义端点
- 未配置自定义端点时使用的 OpenRouter 回退路径
这种区分对以下场景尤为重要:
- 本地模型服务器
- 非 OpenRouter 的 OpenAI 兼容 API
- 无需重新运行 setup 即可切换 provider
- 通过 config 保存的自定义端点,即使当前 shell 中未导出
OPENAI_BASE_URL也应正常工作
原生 Anthropic 路径
Anthropic 不再仅限于"通过 OpenRouter"访问。
当 provider 解析选择 anthropic 时,Hermes 使用:
api_mode = anthropic_messages- 原生 Anthropic Messages API
agent/anthropic_adapter.py进行转换
原生 Anthropic 的凭据解析现在在两者同时存在时,优先使用可刷新的 Claude Code 凭据,而非复制的环境变量 token。实际效果为:
- 包含可刷新认证的 Claude Code 凭据文件被视为首选来源
- 手动设置的
ANTHROPIC_TOKEN/CLAUDE_CODE_OAUTH_TOKEN值仍可作为显式覆盖 - Hermes 在调用原生 Messages API 前会预检 Anthropic 凭据刷新
- Hermes 在重建 Anthropic 客户端后,仍会在收到 401 时重试一次,作为回退路径
OpenAI Codex 路径
Codex 使用独立的 Responses API 路径:
api_mode = codex_responses- 专用的凭据解析和认证存储支持
辅助模型路由
辅助任务包括:
- 视觉
- 网页提取摘要
- 上下文压缩摘要
- skills hub 操作
- MCP 辅助操作
- 记忆刷新
这些任务可以使用各自独立的 provider/模型路由,而非主对话模型。
当辅助任务配置的 provider 为 main 时,Hermes 通过与普通对话相同的共享运行时路径进行解析。实际效果为:
- 环境变量驱动的自定义端点仍然有效
- 通过
hermes model/config.yaml保存的自定义端点同样有效 - 辅助路由能够区分真实保存的自定义端点与 OpenRouter 回退
回退模型
Hermes 支持配置回退 provider 链------一个按顺序尝试的 (provider, model) 条目列表,当主模型遇到错误时依次尝试。旧版单对 fallback_model 字典仍被接受以保持向后兼容(并在首次写入时迁移)。
内部工作原理
-
存储 :
AIAgent.__init__存储fallback_model字典并将_fallback_activated设为False。 -
触发点 :
_try_activate_fallback()在run_agent.py主重试循环的三处被调用:- 在无效 API 响应(None choices、缺少 content)达到最大重试次数后
- 在不可重试的客户端错误(HTTP 401、403、404)时
- 在瞬时错误(HTTP 429、500、502、503)达到最大重试次数后
-
激活流程 (
_try_activate_fallback):- 若已激活或未配置,立即返回
False - 调用
auxiliary_client.py中的resolve_provider_client()构建带有正确认证的新客户端 - 确定
api_mode:openai-codex 使用codex_responses,anthropic 使用anthropic_messages,其余使用chat_completions - 原地替换:
self.model、self.provider、self.base_url、self.api_mode、self.client、self._client_kwargs - 对于 anthropic 回退:构建原生 Anthropic 客户端而非 OpenAI 兼容客户端
- 重新评估 prompt 缓存(对 OpenRouter 上的 Claude 模型启用)
- 将
_fallback_activated设为True------防止再次触发 - 将重试计数重置为 0 并继续循环
- 若已激活或未配置,立即返回
-
配置流程:
- CLI:
cli.py读取CLI_CONFIG["fallback_model"]→ 传递给AIAgent(fallback_model=...) - Gateway:
gateway/run.py._load_fallback_model()读取config.yaml→ 传递给AIAgent - 验证:
provider和model键均须非空,否则回退被禁用
- CLI:
不支持回退的场景
- 子代理委托 (
tools/delegate_tool.py):子代理继承父代理的 provider,但不继承回退配置 - 辅助任务:使用各自独立的 provider 自动检测链(见上方辅助模型路由)
Cron 任务支持 回退:run_job() 从 config.yaml 读取 fallback_providers(或旧版 fallback_model)并传递给 AIAgent(fallback_model=...),与 gateway 的 _load_fallback_model() 模式一致。参见 Cron 内部机制。
怎么添加一个模型插件
Each subdirectory is a self-contained provider profile plugin. The
directory layout mirrors `plugins/platforms/`:
```
plugins/model-providers/
├── openrouter/
│ ├── __init__.py # registers the ProviderProfile
│ └── plugin.yaml # manifest: name, kind, version, description
├── anthropic/
│ ├── __init__.py
│ └── plugin.yaml
└── ...
```
## How discovery works
`providers/__init__.py._discover_providers()` scans this directory (and
`$HERMES_HOME/plugins/model-providers/`) the first time anything calls
`get_provider_profile()` or `list_providers()`. Each `__init__.py` is
imported and expected to call `providers.register_provider(profile)`.
User plugins at `$HERMES_HOME/plugins/model-providers/<name>/` override
bundled plugins of the same name — last-writer-wins in
`register_provider()`. Drop a file there to replace a built-in.
## Adding a new provider
1. Create `plugins/model-providers/<your_provider>/__init__.py`:
```python
from providers import register_provider
from providers.base import ProviderProfile
my_provider = ProviderProfile(
name="your-provider",
aliases=("alias1", "alias2"),
display_name="Your Provider",
description="One-line description shown in the setup picker",
signup_url="https://your-provider.example.com/keys",
env_vars=("YOUR_PROVIDER_API_KEY", "YOUR_PROVIDER_BASE_URL"),
base_url="https://api.your-provider.example.com/v1",
default_aux_model="your-cheap-model",
)
register_provider(my_provider)
```
2. Create `plugins/model-providers/<your_provider>/plugin.yaml`:
```yaml
name: your-provider-profile
kind: model-provider
version: 1.0.0
description: Short sentence about the provider
author: Your Name
```
Nothing else needs to change. `auth.py`, `config.py`, `models.py`,
`doctor.py`, `model_metadata.py`, `runtime_provider.py`, and the
chat_completions transport all auto-wire from the registry.
## Non-trivial profiles
Override the `ProviderProfile` hooks in a subclass for per-provider
quirks — see `plugins/model-providers/openrouter/__init__.py` for
`build_extra_body` and `build_api_kwargs_extras` examples, and
`plugins/model-providers/gemini/__init__.py` for `thinking_config`
translation.