构建模型提供者插件
模型提供者插件声明了一个推理后端——可以是一个兼容 OpenAI 的端点、一个 Anthropic Messages 服务器、一个 Codex 风格的 Responses API,或者一个原生的 Bedrock 接口——Hermes 可以通过它来路由 AIAgent 的调用。每个内置的提供者(OpenRouter、Anthropic、GMI、DeepSeek、Nvidia 等)都是以这种插件的形式交付的。第三方可以通过在 $HERMES_HOME/plugins/model-providers/ 下放入一个目录来添加自己的提供者,无需对仓库进行任何修改。
发现机制如何工作
providers/__init__.py._discover_providers() 在任何代码首次调用 get_provider_profile() 或 list_providers() 时惰性运行。发现顺序如下:
- 捆绑插件 —
<repo>/plugins/model-providers/<name>/— 随 Hermes 一起交付 - 用户插件 —
$HERMES_HOME/plugins/model-providers/<name>/— 可放入任意目录;后续会话无需重启即可生效 - 旧版单文件 —
<repo>/providers/<name>.py— 用于支持仓库外的可编辑安装的向后兼容性
用户插件会覆盖同名的捆绑插件,因为 register_provider() 采用“最后写入者胜出”的规则。放入一个 $HERMES_HOME/plugins/model-providers/gmi/ 目录即可替换内置的 GMI 配置,而无需修改仓库。
plugins/model-providers/my-provider/
├── __init__.py # 在模块级别调用 register_provider(profile)
├── plugin.yaml # kind: model-provider + 元数据(可选但推荐)
└── README.md # 设置说明(可选)
唯一必需的文件是 __init__.py。plugin.yaml 供 hermes plugins 进行内省,也供通用的 PluginManager 将插件路由到正确的加载器;没有它时,通用加载器会回退到源文本启发式方法。
最小示例 —— 一个简单的 API 密钥提供商
# plugins/model-providers/acme-inference/__init__.py
from providers import register_provider
from providers.base import ProviderProfile
acme = ProviderProfile(
name="acme-inference",
aliases=("acme",),
display_name="Acme Inference",
description="Acme — OpenAI 兼容的直接 API",
signup_url="https://acme.example.com/keys",
env_vars=("ACME_API_KEY", "ACME_BASE_URL"),
base_url="https://api.acme.example.com/v1",
auth_type="api_key",
default_aux_model="acme-small-fast",
fallback_models=(
"acme-large-v3",
"acme-medium-v3",
"acme-small-fast",
),
)
register_provider(acme)
# plugins/model-providers/acme-inference/plugin.yaml
name: acme-inference
kind: model-provider
version: 1.0.0
description: Acme Inference — OpenAI 兼容的直接 API
author: Your Name
就这样。放入这两个文件后,以下自动连接会在无需其他编辑的情况下完成:
| 集成 | 位置 | 获得什么 |
|---|---|---|
| 凭证解析 | hermes_cli/auth.py | PROVIDER_REGISTRY["acme-inference"] 根据配置文件填充 |
--provider CLI 标志 | hermes_cli/main.py | 接受 acme-inference |
hermes model 选择器 | hermes_cli/models.py | 出现在 CANONICAL_PROVIDERS 中,模型列表从 {base_url}/models 获取 |
hermes doctor | hermes_cli/doctor.py | 对 ACME_API_KEY 进行健康检查 + {base_url}/models 探测 |
hermes setup | hermes_cli/config.py | ACME_API_KEY 出现在 OPTIONAL_ENV_VARS 和设置向导中 |
| URL 反向映射 | agent/model_metadata.py | 主机名 → 提供商名称,用于自动检测 |
| 辅助模型 | agent/auxiliary_client.py | 使用 default_aux_model 进行压缩/总结 |
| 运行时解析 | hermes_cli/runtime_provider.py | 返回正确的 base_url、api_key、api_mode |
| 传输 | agent/transports/chat_completions.py | 配置文件路径通过 prepare_messages / build_extra_body / build_api_kwargs_extras 生成 kwargs |
ProviderProfile 字段
完整定义在 providers/base.py 中。最有用的是:
| 字段 | 类型 | 用途 |
|---|---|---|
name | str | 规范 ID — 与 config.yaml 中的 model.provider 和 --provider 标志匹配 |
aliases | tuple[str, ...] | 由 get_provider_profile() 解析的替代名称(例如 grok → xai) |
api_mode | str | chat_completions | codex_responses | anthropic_messages | bedrock_converse |
display_name | str | hermes model 选择器中显示的人类可读标签 |
description | str | 选择器副标题 |
signup_url | str | 在首次运行设置期间显示(“在此处获取 API 密钥”) |
env_vars | tuple[str, ...] | 按优先级顺序排列的 API 密钥环境变量;最后一个 *_BASE_URL 条目用作用户基础 URL 覆盖 |
base_url | str | 默认推理端点 |
models_url | str | 显式目录 URL(回退到 {base_url}/models) |
auth_type | str | api_key | oauth_device_code | oauth_external | copilot | aws_sdk | external_process |
fallback_models | tuple[str, ...] | 当实时目录获取失败时显示的精选列表 |
default_headers | dict[str, str] | 每个请求都发送(例如 Copilot 的 Editor-Version) |
fixed_temperature | Any | None = 使用调用者的值;OMIT_TEMPERATURE 哨兵 = 根本不发送温度(Kimi) |
default_max_tokens | int | None | 提供商级别的 max_tokens 上限(Nvidia: 16384) |
default_aux_model | str | 用于辅助任务(压缩、视觉、总结)的廉价模型 |
可覆盖的钩子
对于非平凡的特性,可子类化 ProviderProfile:
from typing import Any
from providers.base import ProviderProfile
class AcmeProfile(ProviderProfile):
def prepare_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""提供商特定的消息预处理。在 codex
清理之后、开发者角色交换之前运行。默认:透传。"""
# 示例:Qwen 将纯文本内容规范化为部件列表
# 数组并注入 cache_control;Kimi 重写工具调用 JSON
return messages
def build_extra_body(self, *, session_id=None, **context) -> dict:
"""提供商特定的 extra_body 字段,合并到 API 调用中。
上下文包括:session_id、provider_preferences、model、base_url、
reasoning_config。默认:空字典。"""
# 示例:OpenRouter 的提供商偏好块,
# Gemini 的 thinking_config 转换。
return {}
def build_api_kwargs_extras(self, *, reasoning_config=None, **context):
"""返回 (extra_body_additions, top_level_kwargs)。当某些字段
在顶层(Kimi 的 reasoning_effort)而某些在 extra_body
(OpenRouter 的 reasoning dict)中时需要。默认:({}, {})。"""
return {}, {}
def fetch_models(self, *, api_key=None, timeout=8.0) -> list[str] | None:
"""实时目录获取。默认使用 Bearer 认证访问 {models_url or base_url}/models。
为以下情况覆盖:自定义认证(Anthropic)、无 REST 端点
(Bedrock → None)、或公共/未认证目录(OpenRouter)。"""
return super().fetch_models(api_key=api_key, timeout=timeout)
钩子参考示例
查看这些内置插件了解惯用写法:
| 插件 | 为何查看 |
|---|---|
plugins/model-providers/openrouter/ | 带提供商偏好的聚合器、公共模型目录 |
plugins/model-providers/gemini/ | thinking_config 转换(原生 + OpenAI 兼容嵌套形式) |
plugins/model-providers/kimi-coding/ | OMIT_TEMPERATURE、extra_body.thinking、顶层 reasoning_effort |
plugins/model-providers/qwen-oauth/ | 消息规范化、cache_control 注入、VL 高分辨率 |
plugins/model-providers/nous/ | 归因标签、“禁用时省略推理” |
plugins/model-providers/custom/ | Ollama num_ctx + think: false 特性 |
plugins/model-providers/bedrock/ | api_mode="bedrock_converse"、fetch_models 返回 None(无 REST 端点) |
用户覆盖 —— 无需编辑仓库即可替换内置项
假设你想将 gmi 指向你的私有暂存端点进行测试。创建 ~/.hermes/plugins/model-providers/gmi/__init__.py:
from providers import register_provider
from providers.base import ProviderProfile
register_provider(ProviderProfile(
name="gmi",
aliases=("gmi-cloud", "gmicloud"),
env_vars=("GMI_API_KEY",),
base_url="https://gmi-staging.internal.example.com/v1",
auth_type="api_key",
default_aux_model="google/gemini-3.1-flash-lite-preview",
))
下次会话时,get_provider_profile("gmi").base_url 将返回暂存 URL。无需修补仓库,无需重建。因为用户插件在内置插件之后被发现,用户的 register_provider() 调用会生效。
api_mode 选择
识别四个值。Hermes 根据以下规则选择:
- 用户显式覆盖(
config.yaml中设置的model.api_mode) - OpenCode 的每模型分派(用于 Zen 和 Go 的
opencode_model_api_mode) - URL 自动检测 —
/anthropic后缀 →anthropic_messages、api.openai.com→codex_responses、api.x.ai→codex_responses、Kimi 域上的/coding→chat_completions - 配置文件
api_mode作为 URL 检测未找到匹配时的回退 - 默认
chat_completions
设置 profile.api_mode 以匹配你的提供商提供的默认值——它作为一个提示。用户的 URL 覆盖仍然优先。
认证类型
auth_type | 含义 | 谁使用它 |
|---|---|---|
api_key | 单个环境变量携带静态 API 密钥 | 大多数提供商 |
oauth_device_code | 设备代码 OAuth 流程 | — |
oauth_external | 用户在其他地方登录,令牌落入 auth.json | Anthropic OAuth、MiniMax OAuth、Gemini Cloud Code、Qwen Portal、Nous Portal |
copilot | GitHub Copilot 令牌刷新周期 | 仅 copilot 插件 |
aws_sdk | AWS SDK 凭证链(IAM 角色、配置文件、环境变量) | 仅 bedrock 插件 |
external_process | 认证由智能体生成的子进程处理 | 仅 copilot-acp 插件 |
auth_type 决定了哪些代码路径将你的提供商视为“简单的 API 密钥提供商”——如果不是 api_key,PluginManager 仍会记录清单,但 Hermes 的 CLI 级别自动化(doctor 检查、--provider 标志、设置向导委派)可能会跳过它。
## 发现时机
提供者发现是**延迟的** —— 由进程中的首次 `get_provider_profile()` 或 `list_providers()` 调用触发。实际上,这在启动时就会发生(`auth.py` 模块加载时会预先扩展 `PROVIDER_REGISTRY`)。如果你需要验证你的插件是否已加载,请运行:
```bash
hermes doctor
—— 一个成功的 auth_type="api_key" 配置文件会出现在 Provider Connectivity 部分,并附带一个 /models 探测。
要进行编程检查:
from providers import list_providers
for p in list_providers():
print(p.name, p.base_url, p.api_mode)
测试你的插件
将 HERMES_HOME 指向一个临时目录,以免污染你的真实配置:
export HERMES_HOME=/tmp/hermes-plugin-test
mkdir -p $HERMES_HOME/plugins/model-providers/my-provider
cat > $HERMES_HOME/plugins/model-providers/my-provider/__init__.py <<'EOF'
from providers import register_provider
from providers.base import ProviderProfile
register_provider(ProviderProfile(
name="my-provider",
env_vars=("MY_API_KEY",),
base_url="https://api.my-provider.example.com/v1",
auth_type="api_key",
))
EOF
export MY_API_KEY=your-test-key
hermes -z "hello" --provider my-provider -m some-model
通用 PluginManager 集成
通用的 PluginManager(即 hermes plugins 操作的对象)能看到模型提供者插件,但不会导入它们 —— providers/__init__.py 管理着它们的生命周期。管理器会记录清单以便自省,并按 kind: model-provider 进行分类。当你将一个未标记的用户插件放入 $HERMES_HOME/plugins/ 中,而它恰好用 ProviderProfile 调用了 register_provider 时,管理器会通过源文本启发式方法自动将其强制转换为 kind: model-provider —— 因此,即使没有 plugin.yaml,插件也能正确路由。
通过 pip 分发
像任何 Hermes 插件一样,模型提供者可以作为 pip 包发布。在你的 pyproject.toml 中添加一个入口点:
[project.entry-points."hermes.plugins"]
acme-inference = "acme_hermes_plugin:register"
……其中 acme_hermes_plugin:register 是一个调用 register_provider(profile) 的函数。通用的 PluginManager 会在 discover_and_load() 期间发现并加载入口点插件。对于 kind: model-provider 的 pip 插件,你仍然需要在清单中声明类型(或依赖源文本启发式方法)。
参见 构建 Hermes 插件 了解完整的入口点设置。
相关页面
- 提供者运行时 — 决议优先级 + 每层读取配置文件的位置
- 添加提供者 — 新推理后端的端到端清单(涵盖快速插件路径和完整的 CLI/认证集成)
- 记忆提供者插件
- 上下文引擎插件
- 构建 Hermes 插件 — 通用插件开发