hermes源码学习5-Provider 运行时解析

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 + 注册表入口点(ProviderProfileregister_providerget_provider_profilelist_providers
  • plugins/model-providers/<name>/ --- 每个 provider 的插件(内置),声明 api_modebase_urlenv_varsfallback_models 并在首次访问时将自身注册到注册表。用户插件位于 $HERMES_HOME/plugins/model-providers/<name>/,会覆盖同名的内置插件。

providers/ 中的 get_provider_profile() 为给定 provider id 返回一个 ProviderProfileruntime_provider.py 在解析时调用它,以获取规范的 base_urlenv_vars 优先级列表、api_modefallback_models,无需在多个文件中重复这些数据。在 plugins/model-providers/<your-provider>/(或 $HERMES_HOME/plugins/model-providers/<your-provider>/)下添加一个调用 register_provider() 的新插件,即可让 runtime_provider.py 自动识别它------无需在解析器本身中添加分支。

如果你想添加一个新的一等推理 provider,请结合本页阅读 添加 ProviderModel Provider 插件指南

解析优先级

从高层来看,provider 解析使用以下顺序:

  1. 显式 CLI/运行时请求
  2. config.yaml 中的模型/provider 配置
  3. 环境变量
  4. provider 特定的默认值或自动解析

该顺序很重要,因为 Hermes 将已保存的模型/provider 选择视为正常运行的真实来源。这可以防止过时的 shell 导出变量悄悄覆盖用户在 hermes model 中最后选择的端点。

Provider

当前 provider 系列包括(完整内置集合见 plugins/model-providers/):

  • OpenRouter
  • Nous Portal
  • OpenAI Codex
  • Copilot / Copilot ACP
  • Anthropic(原生)
  • Google / Gemini(geminigoogle-gemini-cli
  • Alibaba / DashScope(alibabaalibaba-coding-plan
  • DeepSeek
  • Z.AI
  • Kimi / Moonshot(kimi-codingkimi-coding-cn
  • MiniMax(minimaxminimax-cnminimax-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 列表)

运行时解析的输出

运行时解析器返回的数据包括:

  • provider
  • api_mode
  • base_url
  • api_key
  • source
  • provider 特定的元数据,如过期/刷新信息

为什么这很重要

该解析器是 Hermes 能够在以下场景之间共享认证/运行时逻辑的主要原因:

  • hermes chat
  • gateway 消息处理
  • 在全新会话中运行的 cron 任务
  • ACP 编辑器会话
  • 辅助模型任务

OpenRouter 与自定义 OpenAI 兼容 base URL

Hermes 包含相关逻辑,以避免在存在多个 provider 密钥时(例如同时存在 OPENROUTER_API_KEYOPENAI_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 字典仍被接受以保持向后兼容(并在首次写入时迁移)。

内部工作原理

  1. 存储AIAgent.__init__ 存储 fallback_model 字典并将 _fallback_activated 设为 False

  2. 触发点_try_activate_fallback()run_agent.py 主重试循环的三处被调用:

    • 在无效 API 响应(None choices、缺少 content)达到最大重试次数后
    • 在不可重试的客户端错误(HTTP 401、403、404)时
    • 在瞬时错误(HTTP 429、500、502、503)达到最大重试次数后
  3. 激活流程_try_activate_fallback):

    • 若已激活或未配置,立即返回 False
    • 调用 auxiliary_client.py 中的 resolve_provider_client() 构建带有正确认证的新客户端
    • 确定 api_mode:openai-codex 使用 codex_responses,anthropic 使用 anthropic_messages,其余使用 chat_completions
    • 原地替换:self.modelself.providerself.base_urlself.api_modeself.clientself._client_kwargs
    • 对于 anthropic 回退:构建原生 Anthropic 客户端而非 OpenAI 兼容客户端
    • 重新评估 prompt 缓存(对 OpenRouter 上的 Claude 模型启用)
    • _fallback_activated 设为 True------防止再次触发
    • 将重试计数重置为 0 并继续循环
  4. 配置流程

    • CLI:cli.py 读取 CLI_CONFIG["fallback_model"] → 传递给 AIAgent(fallback_model=...)
    • Gateway:gateway/run.py._load_fallback_model() 读取 config.yaml → 传递给 AIAgent
    • 验证:providermodel 键均须非空,否则回退被禁用

不支持回退的场景

  • 子代理委托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.
相关推荐
2601_961963381 小时前
从OCR到NLP:AI技术如何赋能电子合同智能审核与风险预警?
网络·人工智能·安全·金融·智能合约
apcipot_rain1 小时前
计科八股20260611——推荐系统协同过滤、信息安全、团队协作、知识图谱
人工智能·知识图谱
谷哥的小弟1 小时前
大模型核心基础知识(18)—Transformer模型的提出背景
人工智能·深度学习·神经网络·大模型·transformer·大语言模型
-To be number.wan1 小时前
计算机组成原理 | 指令寻址
学习·计算机组成原理
CJH(本人账号)1 小时前
大模型的“越狱“之路:从DAN到多模态注入,AI安全边界正在崩塌
网络·人工智能·安全
Niuguangshuo1 小时前
LangChain 学习之旅(二):用 LCEL 与解析器构建标准流水线
学习·langchain·unix
Chenyu_3101 小时前
世界模型:从“预测下一个状态”到“可交互世界模拟器”
人工智能·计算机视觉·交互
叫我:松哥1 小时前
基于神经网络的汽车与自行车的分类算法设计与实现,采用ResNet50和迁移学习,准确率达到99%
人工智能·python·神经网络·机器学习·分类·汽车·迁移学习
五度易链-区域产业数字化管理平台1 小时前
从编制规范到落地应用,新版可研报告撰写全流程指南
大数据·人工智能