Files
2026-06-14 14:30:48 -04:00

202 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
sidebar_position: 4
title: "Provider 运行时解析"
description: "Hermes 如何在运行时解析 provider、凭据、API 模式及辅助模型"
---
# 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 + 注册表入口点(`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](./adding-providers.md) 和 [Model Provider 插件指南](./model-provider-plugin.md)。
## 解析优先级
从高层来看,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`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
- xAIGrok
- 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_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` 字典仍被接受以保持向后兼容(并在首次写入时迁移)。
### 内部工作原理
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.model``self.provider``self.base_url``self.api_mode``self.client``self._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`
- 验证:`provider``model` 键均须非空,否则回退被禁用
### 不支持回退的场景
- **子代理委托**`tools/delegate_tool.py`):子代理继承父代理的 provider,但不继承回退配置
- **辅助任务**:使用各自独立的 provider 自动检测链(见上方辅助模型路由)
Cron 任务**支持**回退:`run_job()``config.yaml` 读取 `fallback_providers`(或旧版 `fallback_model`)并传递给 `AIAgent(fallback_model=...)`,与 gateway 的 `_load_fallback_model()` 模式一致。参见 [Cron 内部机制](./cron-internals.md)。
### 测试覆盖
参见 `tests/test_fallback_model.py`,其中包含覆盖所有支持 provider、单次触发语义及边界情况的完整测试。
## 相关文档
- [Agent 循环内部机制](./agent-loop.md)
- [ACP 内部机制](./acp-internals.md)
- [上下文压缩与 Prompt 缓存](./context-compression-and-caching.md)