構建模型提供商插件
模型提供商插件聲明一個推理後端——一個兼容 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 # Calls register_provider(profile) at module-level
├── plugin.yaml # kind: model-provider + metadata (optional but recommended)
└── README.md # Setup instructions (optional)
唯一必需的文件是 __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-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
就是這樣。放置這兩個文件後,以下功能將自動連接,無需其他編輯:
| 集成 | 位置 | 獲得的功能 |
|---|---|---|
| 憑證解析 | 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 哨兵值 = 根本不發送 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]]:
"""Provider-specific message preprocessing. Runs after codex
sanitization, before developer-role swap. Default: pass-through."""
# Example: Qwen normalizes plain-text content to a list-of-parts
# array and injects cache_control; Kimi rewrites 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."""
# Example: OpenRouter's provider-preferences block,
# Gemini's 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, OpenRouter's verbosity for
adaptive Anthropic models) 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)
鉤子參考示例
查看這些捆綁插件以瞭解慣用法:
| 插件 | 為何值得關注 |
|---|---|
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 以匹配你的提供商默認的 API 模式 — 它作為一個提示。用戶 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)。如果你需要驗證插件是否已加載,請運行:
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_agent.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 插件 — 通用插件創作