建立模型 Provider 外掛
模型 provider 外掛宣告一個推論後端 — 一個 OpenAI 相容的端點、一個 Anthropic Messages 伺服器、一個 Codex 風格的 Responses API,或一個 Bedrock 原生介面 — Hermes 可以透過它路由 AIAgent 呼叫。每個內建 provider(OpenRouter、Anthropic、GMI、DeepSeek、Nvidia……)都以外掛形式發佈。第三方可以透過在 $HERMES_HOME/plugins/model-providers/ 下放入一個目錄來新增自己的,無需對 repo 做任何修改。
提示
模型 provider 外掛是第三種 provider 外掛。其他兩種是 Memory Provider Plugins(跨 session 知識)和 Context Engine Plugins(上下文壓縮策略)。三者遵循相同的「放入目錄、宣告設定檔、不修改 repo」模式。
發現機制如何運作
providers/__init__.py._discover_providers() 在程式中首次呼叫 get_provider_profile() 或 list_providers() 時延遲執行。發現順序:
- 內建外掛 —
<repo>/plugins/model-providers/<name>/— 隨 Hermes 一起發佈 - 使用者外掛 —
$HERMES_HOME/plugins/model-providers/<name>/— 放入任何目錄;後續 session 不需要重新啟動 - 舊式單檔案 —
<repo>/providers/<name>.py— 為樹外可編輯安裝提供向後相容
使用者外掛會覆寫同名的內建外掛,因為 register_provider() 是後寫者優先的。放入一個 $HERMES_HOME/plugins/model-providers/gmi/ 目錄即可取代內建的 GMI 設定檔而無需修改 repo。
目錄結構
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 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
就這樣。放入這兩個檔案後,以下內容會自動連線,無需其他修改:
| 整合點 | 位置 | 取得的內容 |
|---|---|---|
| 認證解析 | 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 | 主機名 → provider 名稱用於自動偵測 |
| 輔助模型 | 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 key」) |
env_vars | tuple[str, ...] | 按優先順序排列的 API key 環境變數;最後的 *_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 | Provider 級別的 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 特定的訊息預處理。在 codex 清理後、
開發者角色交換前執行。預設:直接通過。"""
# 範例:Qwen 將純文字內容正規化為部分列表陣列
# 並注入 cache_control;Kimi 重寫工具呼叫 JSON
return messages
def build_extra_body(self, *, session_id=None, **context) -> dict:
"""Provider 特定的 extra_body 欄位,合併到 API 呼叫中。
上下文包含:session_id、provider_preferences、model、base_url、
reasoning_config。預設:空字典。"""
# 範例:OpenRouter 的 provider-preferences 區塊、
# Gemini 的 thinking_config 翻譯。
return {}
def build_api_kwargs_extras(self, *, reasoning_config=None, **context):
"""回傳 (extra_body_additions, top_level_kwargs)。當某些
欄位進入頂層(Kimi 的 reasoning_effort、OpenRouter 針對
自適應 Anthropic 模型的 verbosity)而某些進入 extra_body
(OpenRouter 的 reasoning 字典)時需要。預設:({}, {})。"""
return {}, {}
def fetch_models(self, *, api_key=None, timeout=8.0) -> list[str] | None:
"""即時目錄取得。預設使用 Bearer 認證命中
{models_url 或 base_url}/models。需要以下情況時覆寫:自訂認證
(Anthropic)、無 REST 端點(Bedrock → None)、或公開/無認證目錄
(OpenRouter)。"""
return super().fetch_models(api_key=api_key, timeout=timeout)
鉤子參考範例
參考這些內建外掛以了解慣用法:
| 外掛 | 為什麼要看 |
|---|---|
plugins/model-providers/openrouter/ | 帶有 provider 偏好設定的聚合器、公開模型目錄 |
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 端點) |
使用者覆寫 — 無需修改 repo 即可取代內建
假設你想將 gmi 指向你的私有 staging 端點進行測試。建立 ~/.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 修補,無需重建。因為使用者外掛在內建之後被發現,所以使用者的 register_provider() 呼叫會優先生效。
api_mode 選擇
識別四個值。Hermes 根據以下條件選擇:
- 使用者明確覆寫(設定時的
config.yamlmodel.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 以匹配你的 provider 預設出貨的值 — 它作為提示。使用者 URL 覆寫仍然優先。
認證類型
auth_type | 意義 | 使用者 |
|---|---|---|
api_key | 單一環境變數攜帶靜態 API key | 大多數 provider |
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 門控哪些程式碼路徑將你的 provider 視為「簡單的 api-key provider」 — 如果不是 api_key,PluginManager 仍然會記錄 manifest,但 Hermes 的 CLI 級自動化(doctor 檢查、--provider 旗標、設定精靈委派)可能會跳過它。
發現時機
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 操作的那個)會看到模型 provider 外掛但不會匯入它們 — providers/__init__.py 擁有它們的生命週期。管理器記錄 manifest 用於自省,並按 kind: model-provider 分類。當你將一個未標記的使用者外掛放入 $HERMES_HOME/plugins/ 且它恰好以 ProviderProfile 呼叫 register_provider 時,管理器會透過原始碼文字啟發式自動將其強制轉換為 kind: model-provider — 因此外掛即使沒有 plugin.yaml 也能正確路由。
透過 pip 分發
與任何 Hermes 外掛一樣,模型 provider 可以作為 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 外掛,你仍然需要在 manifest 中宣告 kind(或依賴原始碼文字啟發式)。
完整入口點設定請見 Building a Hermes Plugin。
相關頁面
- Provider Runtime — 解析優先順序 + 各層讀取設定檔的位置
- Adding Providers — 新推論後端的端對端清單(涵蓋快速外掛路徑和完整的 CLI/認證整合)
- Memory Provider Plugins
- Context Engine Plugins
- Building a Hermes Plugin — 通用外掛撰寫指南