Skip to content

构建 Model Provider Plugin

hermes agent 构建 Model Provider Plugin

Model provider plugins 声明一个 inference backend —— OpenAI 兼容端点、Anthropic Messages server、Codex-style Responses API,或 Bedrock-native surface —— Hermes 可以通过它路由 AIAgent 调用。每个内置 provider(OpenRouter、Anthropic、GMI、DeepSeek、Nvidia,……)都是以这些 plugins 之一的形式发布的。第三方可以通过在 $HERMES_HOME/plugins/model-providers/ 下放置一个目录来添加自己的 provider,而无需对仓库进行任何修改。

providers/__init__.py._discover_providers() 会在任何代码首次调用 get_provider_profile()list_providers() 时惰性运行。发现顺序:

  1. Bundled plugins —— <repo>/plugins/model-providers/<name>/ —— 随 Hermes 一起发布
  2. User plugins —— $HERMES_HOME/plugins/model-providers/<name>/ —— 放入任意目录;后续会话无需重启
  3. Legacy single-file —— <repo>/providers/<name>.py —— 为仓库外 editable installs 提供向后兼容

同名的 User plugins 会覆盖 bundled plugins,因为 register_provider() 是 last-writer-wins。放入一个 $HERMES_HOME/plugins/model-providers/gmi/ 目录,就可以替换内置 GMI profile,而无需修改仓库。

plugins/model-providers/my-provider/
├── __init__.py # 在模块级调用 register_provider(profile)
├── plugin.yaml # kind: model-provider + metadata(可选但推荐)
└── README.md # 设置说明(可选)

唯一必需的文件是 __init__.pyplugin.yaml 会被 hermes plugins 用于 introspection,也会被通用 PluginManager 用于将 plugin 路由到正确 loader;如果没有它,通用 loader 会退回到 source-text heuristic。

最小示例 —— 一个简单的 API-key provider

Section titled “最小示例 —— 一个简单的 API-key provider”
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-compatible direct 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-compatible direct API
author: Your Name

就这样。放入这两个文件后,以下内容会自动接线,无需其他编辑:

集成位置获得的内容
Credential resolutionhermes_cli/auth.py从 profile 填充 PROVIDER_REGISTRY["acme-inference"]
--provider CLI flaghermes_cli/main.py接受 acme-inference
hermes model pickerhermes_cli/models.py出现在 CANONICAL_PROVIDERS 中,model list 从 {base_url}/models 获取
hermes doctorhermes_cli/doctor.pyACME_API_KEY{base_url}/models probe 进行 health check
hermes setuphermes_cli/config.pyACME_API_KEY 出现在 OPTIONAL_ENV_VARS 和 setup wizard 中
URL reverse-mappingagent/model_metadata.pyHostname → provider name,用于 auto-detection
Auxiliary modelagent/auxiliary_client.py使用 default_aux_model 进行 compression / summarization
Runtime resolutionhermes_cli/runtime_provider.py返回正确的 base_urlapi_keyapi_mode
Transportagent/transports/chat_completions.pyProfile path 通过 prepare_messages / build_extra_body / build_api_kwargs_extras 生成 kwargs

完整定义在 providers/base.py 中。最常用的字段如下:

字段类型用途
namestr规范 id —— 匹配 --provider choices 和 HERMES_INFERENCE_PROVIDER
aliasestuple[str, ...]get_provider_profile() 解析的替代名称(例如 grok → xai
api_modestrchat_completions
display_namestrhermes model picker 中显示的人类可读标签
descriptionstrPicker subtitle
signup_urlstr首次运行 setup 时显示(“get an API key here”)
env_varstuple[str, ...]按优先级排列的 API-key env vars;最后一个 *_BASE_URL 条目会用作用户 base-URL override
base_urlstr默认 inference endpoint
models_urlstr显式 catalog URL(fallback 到 {base_url}/models
auth_typestrapi_key
fallback_modelstuple[str, ...]live catalog fetch 失败时显示的精选列表
default_headersdict[str, str]每次请求都会发送(例如 Copilot 的 Editor-Version
fixed_temperatureAnyNone = 使用 caller 的值;OMIT_TEMPERATURE sentinel = 完全不发送 temperature(Kimi)
default_max_tokensint | NoneProvider-level max_tokens 上限(Nvidia:16384)
default_aux_modelstr用于辅助任务的低成本模型(compression、vision、summarization)

对于非平凡的特殊行为,请子类化 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]]:
"""Provider-specific message preprocessing. Runs after codex
sanitization, before developer-role swap. Default: pass-through."""
# 示例:Qwen 会将 plain-text content 规范化为 list-of-parts
# array,并注入 cache_control;Kimi 会重写 tool-call JSON
return messages
def build_extra_body(self, *, session_id=None, **context) -> dict:
"""Provider-specific extra_body fields merged into the API call.
Context includes: session_id, provider_preferences, model, base_url,
reasoning_config. Default: empty dict."""
# 示例:OpenRouter 的 provider-preferences block,
# Gemini 的 thinking_config translation。
return {}
def build_api_kwargs_extras(self, *, reasoning_config=None, **context):
"""Returns (extra_body_additions, top_level_kwargs). Needed when some
fields go top-level (Kimi's reasoning_effort) and some go in extra_body
(OpenRouter's reasoning dict). Default: ({}, {})."""
return {}, {}
def fetch_models(self, *, api_key=None, timeout=8.0) -> list[str] | None:
"""Live catalog fetch. Default hits {models_url or base_url}/models with
Bearer auth. Override for: custom auth (Anthropic), no REST endpoint
(Bedrock → None), or public/unauthenticated catalogs (OpenRouter)."""
return super().fetch_models(api_key=api_key, timeout=timeout)

查看这些 bundled plugins 以了解惯用法:

Plugin为什么查看
plugins/model-providers/openrouter/带 provider preferences、public model catalog 的 aggregator
plugins/model-providers/gemini/thinking_config translation(native + OpenAI-compat nested forms)
plugins/model-providers/kimi-coding/OMIT_TEMPERATUREextra_body.thinking、top-level reasoning_effort
plugins/model-providers/qwen-oauth/Message normalization、cache_control injection、VL high-res
plugins/model-providers/nous/Attribution tags、“omit reasoning when disabled”
plugins/model-providers/custom/Ollama num_ctx + think: false 特殊行为
plugins/model-providers/bedrock/api_mode="bedrock_converse"fetch_models 返回 None(无 REST endpoint)

User overrides —— 不编辑 repo 即可替换 built-in

Section titled “User overrides —— 不编辑 repo 即可替换 built-in”

假设你想将 gmi 指向你的 private staging endpoint 进行测试。创建 ~/.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",
))

下一个 session 中,get_provider_profile("gmi").base_url 会返回 staging URL。无需 repo patch,无需 rebuild。因为 user plugins 会在 bundled plugins 之后被发现,所以 user 的 register_provider() 调用会获胜。

识别四种值。Hermes 基于以下内容选择:

  1. User explicit override(设置了 config.yaml model.api_mode 时)
  2. OpenCode 的 per-model dispatch(Zen 和 Go 的 opencode_model_api_mode
  3. URL auto-detection —— /anthropic 后缀 → anthropic_messagesapi.openai.comcodex_responsesapi.x.aicodex_responses,Kimi domains 上的 /codingchat_completions
  4. Profile api_mode,作为 URL detection 没有发现内容时的 fallback
  5. 默认 chat_completions

profile.api_mode 设置为你的 provider 发布时的默认值 —— 它起到 hint 的作用。User URL overrides 仍然优先。

auth_type含义谁使用
api_key单个 env var 携带静态 API key大多数 providers
oauth_device_codeDevice-code OAuth flow
oauth_external用户在其他地方登录,tokens 落入 auth.jsonAnthropic OAuth、MiniMax OAuth、Gemini Cloud Code、Qwen Portal、Nous Portal
copilotGitHub Copilot token refresh cycle仅 copilot plugin
aws_sdkAWS SDK credential chain(IAM role、profile、env)仅 bedrock plugin
external_processAuth 由 agent 启动的 subprocess 处理仅 copilot-acp plugin

auth_type 控制哪些 codepaths 会把你的 provider 视为 “simple api-key provider” —— 如果它不是 api_keyPluginManager 仍然会记录 manifest,但 Hermes 的 CLI-level automation(doctor checks、--provider flag、setup wizard delegation)可能会跳过它。

Provider discovery 是惰性的 —— 由进程中首次调用 get_provider_profile()list_providers() 触发。实际中,这通常会在启动早期发生(auth.py module load 会 eager 地扩展 PROVIDER_REGISTRY)。如果你需要验证 plugin 是否已加载,请运行:

Terminal window
hermes doctor

—— 一个成功的 auth_type="api_key" profile 会出现在 Provider Connectivity section 中,并带有 /models probe。

用于程序化检查:

from providers import list_providers
for p in list_providers():
print(p.name, p.base_url, p.api_mode)

HERMES_HOME 指向一个临时目录,这样不会污染你的真实 config:

Terminal window
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(也就是 hermes plugins 操作的东西)可以看到 model-provider plugins,但不会 import 它们 —— providers/__init__.py 拥有它们的 lifecycle。manager 会记录 manifest 用于 introspection,并按 kind: model-provider 分类。当你将一个未标注的 user plugin 放入 $HERMES_HOME/plugins/,而它刚好使用 ProviderProfile 调用了 register_provider 时,manager 会通过 source-text heuristic 自动将其强制转换为 kind: model-provider —— 因此即使没有 plugin.yaml,plugin 仍然会被正确路由。

像任何 Hermes plugin 一样,model providers 可以作为 pip package 发布。向你的 pyproject.toml 添加 entry point:

[project.entry-points."hermes.plugins"]
acme-inference = "acme_hermes_plugin:register"

……其中 acme_hermes_plugin:register 是一个调用 register_provider(profile) 的函数。通用 PluginManager 会在 discover_and_load() 期间拾取 entry-point plugins。对于 kind: model-provider 的 pip plugins,你仍然需要在 manifest 中声明 kind(或依赖 source-text heuristic)。

完整 entry-points setup 请参见 Building a Hermes Plugin。

  • Provider Runtime —— resolution precedence + 每一层从哪里读取 profile
  • Adding Providers —— 新 inference backends 的端到端检查清单(涵盖快速 plugin path 和完整 CLI/auth integration)
  • Memory Provider Plugins
  • Context Engine Plugins
  • Building a Hermes Plugin —— 通用 plugin authoring
-
0:000:00