プロバイダーの追加
目次
- 全体像
- ツール呼び出しの通信形式
- まず実装の道筋を決める
- 道筋 A — OpenAI 互換のプロバイダー
- 道筋 B — ネイティブのプロバイダー
- ファイルのチェックリスト
- 組み込みプロバイダーすべてに必要
- ネイティブ / OpenAI 以外のプロバイダーで追加が必要なもの
- 近道: 単純な API キー方式のプロバイダー
- 本道: OAuth や込み入ったプロバイダー
- 手順 1: 正典となるプロバイダー ID を 1 つ決める
- 手順 2: hermes_cli/auth.py に認証のメタデータを足す
- 手順 3: hermes_cli/models.py にモデルの一覧と別名を足す
- 手順 4: hermes_cli/runtime_provider.py で実行時のデータを解決する
- 手順 5: hermes_cli/main.py で CLI につなぐ
- 手順 6: 補助的な呼び出しを動かし続ける
- agent/auxiliary_client.py
- agent/model_metadata.py
- 手順 7: ネイティブのプロバイダーなら、アダプターと run_agent.py の対応を足す
- 新しいアダプターのファイル
- run_agent.py
- プロンプトキャッシュとプロバイダー固有のリクエスト項目
- 手順 8: テスト
- 手順 9: 実物での確認
- 手順 10: 利用者向けドキュメントを更新する
- OpenAI 互換プロバイダーのチェックリスト
- ネイティブプロバイダーのチェックリスト
- つまずきやすいところ
- 1. 認証には追加したのに、モデルの解釈に追加していない
- 2. config["model"] が文字列にも dict にもなり得ることを忘れる
- 3. 組み込みプロバイダーが必要だと思い込む
- 4. 補助の経路を忘れる
- 5. run_agent.py にネイティブプロバイダーの分岐が隠れている
- 6. OpenRouter だけのつまみを他のプロバイダーへ送る
- 7. hermes model は更新したのに hermes setup は更新していない
- 実装中に検索するとよい語
- 関連ドキュメント
Hermes は独自プロバイダーの経路を使って、OpenAI 互換のエンドポイントならすでに何とでも話せます。組み込みのプロバイダーを追加するのは、そのサービスに一級の使い心地を用意したいときだけにしてください。
- そのプロバイダー固有の認証やトークン更新がある
- 厳選したモデルの一覧を持たせたい
- セットアップや
hermes modelのメニューに項目を出したい provider:modelの書き方で使えるプロバイダーの別名を用意したい- OpenAI とは違う API の形をしていて、アダプターが必要
そのプロバイダーが「OpenAI 互換のベース URL と API キーがもう 1 つあるだけ」なら、名前を付けた独自プロバイダーで足りるかもしれません。
全体像
組み込みのプロバイダーは、いくつかの層にまたがって辻褄を合わせる必要があります。
hermes_cli/auth.pyが、認証情報をどう見つけるかを決めます。hermes_cli/runtime_provider.pyが、それを実行時のデータに変えます。
providerapi_modebase_urlapi_keysource
run_agent.pyがapi_modeを見て、リクエストの組み立て方と送り方を決めます。hermes_cli/models.pyとhermes_cli/main.pyが、そのプロバイダーを CLI に出します(hermes_cli/setup.pyは自動的にmain.pyへ委譲するので、こちらの変更は不要です)。agent/auxiliary_client.pyとagent/model_metadata.pyが、脇のタスクとトークンの見積もりを動かし続けます。
肝になる抽象は api_mode です。
- ほとんどのプロバイダーは
chat_completionsを使います。 - Codex と Meta Model API(
api.meta.ai— Muse Spark)はcodex_responsesを使います(プロンプトキャッシュのためにprompt_cache_retention: 24hを自動で送ります。api.meta.aiが 93〜99% のキャッシュヒットを出せるのは/v1/responsesだけです)。 - Ramp Router(
api.router.com)もcodex_responsesを使います。Responses が Router の本来の通信路で(/v1/chat/completionsは最小限の互換層にすぎません)、しかもモデルごとにreasoning.effortを検証します。router のプロファイルは、稼働中のカタログから各モデルの語彙を宣言することでこれに対応しています(ProviderProfile.supported_reasoning_efforts)。 - Anthropic は
anthropic_messagesを使います。 - OpenAI 以外の新しいプロトコルを足す場合はたいてい、新しいアダプターと新しい
api_modeの分岐を追加することになります。
ツール呼び出しの通信形式
Hermes は会話の履歴を内部的に OpenAI の chat-completions の形で保持しています。そのため chat_completions トランスポートの convert_messages / convert_tools(agent/transports/chat_completions.py)はほぼ恒等変換で、ほかのトランスポートはすべてこの形*から*それぞれのプロトコルへ変換します。この形の正典 — JSON スキーマの parameters を持つ tools の定義、function.arguments を文字列化して持つアシスタントの tool_calls の項目、tool_call_id を鍵にした role: "tool" の結果メッセージ — は OpenAI chat completions API 早見表にあります。ネイティブのアダプターを書くときは、そのページが変換の入力側を定め、プロバイダーのドキュメントが出力側を定めることになります。
まず実装の道筋を決める
道筋 A — OpenAI 互換のプロバイダー
プロバイダーが標準的な chat-completions 形式のリクエストを受け付ける場合は、こちらです。
よくある作業:
- 認証のメタデータを足す
- モデルの一覧と別名を足す
- 実行時の解決を足す
- CLI のメニューにつなぐ
- 補助モデルの既定値を足す
- テストと利用者向けドキュメントを足す
たいていの場合、新しいアダプターや新しい api_mode は不要です。
道筋 B — ネイティブのプロバイダー
プロバイダーが OpenAI の chat completions と同じようには振る舞わない場合は、こちらです。
いまツリーにある例:
codex_responses(OpenAI Codex、xAI Grok、api.meta.ai経由の Meta Muse Spark — こちらはprompt_cache_retention: 24hを自動で送ります — およびapi.router.com経由の Ramp Router)anthropic_messages
この道筋では、道筋 A のすべてに加えて次が必要です。
agent/配下のプロバイダー用アダプター- リクエストの組み立て、送出、使用量の取り出し、割り込みの処理、応答の正規化についての
run_agent.pyの分岐 - アダプターのテスト
ファイルのチェックリスト
組み込みプロバイダーすべてに必要
hermes_cli/auth.pyhermes_cli/models.pyhermes_cli/runtime_provider.pyhermes_cli/main.pyagent/auxiliary_client.pyagent/model_metadata.py- テスト
website/docs/配下の利用者向けドキュメント
ネイティブ / OpenAI 以外のプロバイダーで追加が必要なもの
agent/<provider>_adapter.pyrun_agent.py- プロバイダーの SDK が必要なら
pyproject.toml
近道: 単純な API キー方式のプロバイダー
追加したいプロバイダーが、API キー 1 本で認証する OpenAI 互換のエンドポイントにすぎない場合、auth.py、runtime_provider.py、main.py をはじめ、下の完全なチェックリストにあるファイルには一切触れる必要がありません。
必要なのは次だけです。
plugins/model-providers/<your-provider>/配下のプラグインディレクトリ。中身は次の 2 つです。
__init__.py— モジュールの階層でregister_provider(profile)を呼びますplugin.yaml— マニフェスト(name、kind: model-provider、version、description)
- 以上です。プロバイダーのプラグインは、何かが最初に
get_provider_profile()かlist_providers()を呼んだ時点で自動的に読み込まれます。同梱のプラグイン(このリポジトリ)も、$HERMES_HOME/plugins/model-providers/にある利用者のプラグインも、どちらも拾われます。
プラグインを追加してそれが register_provider() を呼ぶと、次の項目が自動的につながります。
auth.pyのPROVIDER_REGISTRYの項目(認証情報の解決、環境変数の参照)api_modeがchat_completionsに設定されるbase_urlが設定か、宣言された環境変数から取られるenv_varsが API キーを探すときの優先順位どおりに調べられる- そのプロバイダー用の
fallback_modelsの一覧が登録される - CLI の
--providerフラグがそのプロバイダー ID を受け付ける hermes modelのメニューにそのプロバイダーが並ぶhermes setupのウィザードが自動的にmain.pyへ委譲するprovider:modelという別名の書き方が使える- 実行時の解決器が正しい
base_urlとapi_keyを返す - CLI の
--provider <name>フラグがそのプロバイダー ID を受け付ける - フォールバックモデルの起動が、そのプロバイダーへきれいに切り替わる
$HERMES_HOME/plugins/model-providers/<name>/ にある利用者のプラグインは、同じ名前の同梱プラグインを上書きします(register_provider() は後勝ちです)。そのため第三者は、リポジトリを編集しなくても組み込みのプロファイルに手を入れたり、丸ごと差し替えたりできます。
雛形としては plugins/model-providers/nvidia/ や plugins/model-providers/gmi/ を見てください。項目の一覧、フックの書き方、通しの例はモデルプロバイダープラグインのガイドにあります。
本道: OAuth や込み入ったプロバイダー
プロバイダーに次のどれかが必要な場合は、下の完全なチェックリストを使ってください。
- OAuth やトークンの更新(Nous Portal、Codex、Qwen Portal、Copilot)
- 新しいアダプターが要る、OpenAI とは違う API の形(Anthropic Messages、Codex Responses)
- 独自のエンドポイント判定や、複数リージョンの探索(z.ai、Kimi)
- 厳選した静的なモデル一覧、または稼働中の
/modelsの取得 - 独自の認証フローを持つ、プロバイダー固有の
hermes modelメニュー項目
手順 1: 正典となるプロバイダー ID を 1 つ決める
プロバイダー ID を 1 つ選び、どこでもそれを使います。
リポジトリにある例:
openai-codexkimi-codingminimax-cn
その同じ ID が、次の場所すべてに現れるはずです。
hermes_cli/auth.pyのPROVIDER_REGISTRYhermes_cli/models.pyの_PROVIDER_LABELShermes_cli/auth.pyとhermes_cli/models.py両方の_PROVIDER_ALIASEShermes_cli/main.pyの CLI の--providerの選択肢- セットアップ / モデル選択の分岐
- 補助モデルの既定値
- テスト
これらのファイルの間で ID が食い違っていると、プロバイダーは中途半端につながった状態になります。認証は通るのに、/model やセットアップ、実行時の解決が黙って取りこぼす、といった具合です。
手順 2: hermes_cli/auth.py に認証のメタデータを足す
API キー方式のプロバイダーなら、PROVIDER_REGISTRY に ProviderConfig の項目を追加します。中身は次のとおりです。
idnameauth_type="api_key"inference_base_urlapi_key_env_vars- 任意で
base_url_env_var
あわせて _PROVIDER_ALIASES に別名も足します。
既存のプロバイダーを雛形として使ってください。
- 単純な API キーの道筋: Z.AI、MiniMax
- エンドポイント判定つきの API キーの道筋: Kimi、Z.AI
- ネイティブのトークン解決: Anthropic
- OAuth / 認証情報ストアの道筋: Nous、OpenAI Codex
ここで答えを出しておきたい問いは次のとおりです。
- Hermes はどの環境変数を、どの優先順位で調べるべきか?
- そのプロバイダーにはベース URL の上書きが必要か?
- エンドポイントの探索やトークンの更新は要るか?
- 認証情報がないとき、エラーは何と言うべきか?
「API キーを探す」だけでは済まないプロバイダーなら、関係のない分岐にロジックを押し込まず、専用の認証情報の解決処理を追加してください。
手順 3: hermes_cli/models.py にモデルの一覧と別名を足す
メニューと provider:model の書き方でそのプロバイダーが使えるように、プロバイダーの一覧を更新します。
よくある編集箇所:
_PROVIDER_MODELS_PROVIDER_LABELS_PROVIDER_ALIASESlist_available_providers()の中のプロバイダーの表示順- 稼働中の
/modelsの取得に対応するならprovider_model_ids()
プロバイダーがモデルの一覧を稼働中に返せるなら、そちらを優先し、_PROVIDER_MODELS は静的な受け皿として残しておきます。
このファイルは、次のような入力を成立させているものでもあります。
anthropic:claude-sonnet-4-6
kimi:model-nameここに別名がないと、認証は正しく通るのに /model の解釈で失敗する、ということが起こります。
手順 4: hermes_cli/runtime_provider.py で実行時のデータを解決する
resolve_runtime_provider() は、CLI、ゲートウェイ、cron、ACP、補助クライアントが共通して通る経路です。
少なくとも次を含む dict を返す分岐を追加します。
{
"provider": "your-provider",
"api_mode": "chat_completions", # or your native mode
"base_url": "https://...",
"api_key": "...",
"source": "env|portal|auth-store|explicit",
"requested_provider": requested_provider,
}プロバイダーが OpenAI 互換なら、api_mode はたいてい chat_completions のままにしておきます。
API キーの優先順位には注意してください。Hermes には、OpenRouter のキーが無関係なエンドポイントへ漏れるのを防ぐ処理がすでに入っています。新しいプロバイダーも同じように、どのキーをどのベース URL へ渡すのかをはっきりさせるべきです。
手順 5: hermes_cli/main.py で CLI につなぐ
対話式の hermes model の流れに出てくるまで、そのプロバイダーは見つけてもらえません。
hermes_cli/main.py で次を更新します。
provider_labelsの dictselect_provider_and_model()の中のprovidersの一覧- プロバイダーの振り分け(
if selected_provider == ...) --provider引数の選択肢- そのプロバイダーがログイン・ログアウトに対応するなら、その選択肢
_model_flow_<provider>()の関数。当てはまるなら_model_flow_api_key_provider()を使い回してもかまいません
手順 6: 補助的な呼び出しを動かし続ける
ここで関わるファイルは 2 つです。
agent/auxiliary_client.py
直接 API キーを使うプロバイダーなら、安くて速い補助モデルの既定値を _API_KEY_PROVIDER_AUX_MODELS に追加します。
補助タスクには次のようなものがあります。
- 画像の要約
- Web 抽出の要約
- コンテキスト圧縮の要約
- セッション検索の要約
- メモリの書き出し
そのプロバイダーに妥当な補助の既定値がないと、脇のタスクがまずい受け皿に落ちたり、思いがけず高価なメインのモデルを使ったりします。
agent/model_metadata.py
トークンの見積もり、圧縮のしきい値、各種の上限が正気を保てるように、そのプロバイダーのモデルのコンテキスト長を追加します。
手順 7: ネイティブのプロバイダーなら、アダプターと run_agent.py の対応を足す
素の chat completions でないプロバイダーなら、固有のロジックは agent/<provider>_adapter.py に閉じ込めます。
run_agent.py は取りまとめ役に徹させてください。アダプターの補助関数を呼ぶべきであって、ファイルのあちこちでプロバイダー用のペイロードを手組みするべきではありません。
ネイティブのプロバイダーではたいてい、次の場所に手が必要になります。
新しいアダプターのファイル
よくある役割:
- SDK / HTTP クライアントを組み立てる
- トークンを解決する
- OpenAI 形式の会話メッセージを、そのプロバイダーのリクエスト形式へ変換する
- 必要ならツールのスキーマを変換する
- プロバイダーの応答を、
run_agent.pyが期待する形へ戻す - 使用量と終了理由のデータを取り出す
run_agent.py
api_mode を検索して、分岐点をひとつ残らず点検します。最低限、次を確かめてください。
__init__が新しいapi_modeを選ぶこと- そのプロバイダーでクライアントの構築が動くこと
_build_api_kwargs()がリクエストの整え方を知っていること_interruptible_api_call()が正しいクライアント呼び出しへ振り分けること- 割り込みとクライアントの作り直しの経路が動くこと
- 応答の検証がそのプロバイダーの形を受け入れること
- 終了理由の取り出しが正しいこと
- トークン使用量の取り出しが正しいこと
- フォールバックモデルの起動が、新しいプロバイダーへきれいに切り替わること
- 要約の生成とメモリの書き出しの経路がなお動くこと
あわせて run_agent.py の self.client. も検索してください。標準の OpenAI クライアントがある前提のコード経路は、ネイティブのプロバイダーが別のクライアントオブジェクトを使ったり self.client = None になったりすると壊れます。
プロンプトキャッシュとプロバイダー固有のリクエスト項目
プロンプトキャッシュとプロバイダー固有のつまみは、壊れやすい部分です。
すでにツリーにある例:
- Anthropic にはネイティブのプロンプトキャッシュの経路がある
- OpenRouter にはプロバイダー振り分けの項目が渡される
- リクエスト側の選択肢を、すべてのプロバイダーに渡してよいわけではない
ネイティブのプロバイダーを追加するときは、そのプロバイダーが実際に理解できる項目だけを Hermes が送っているかを、念入りに確かめてください。
手順 8: テスト
最低限、プロバイダーの結線を守っているテストには手を入れます。
よくある場所:
tests/hermes_cli/test_runtime_provider_resolution.pytests/cli/test_cli_provider_resolution.pytests/hermes_cli/test_model_switch_custom_providers.py(および隣接するtests/hermes_cli/test_model_switch_*.py)tests/hermes_cli/test_setup_model_provider.pytests/run_agent/test_provider_parity.pytests/run_agent/test_run_agent.py- ネイティブのプロバイダーなら
tests/test_<provider>_adapter.py
ドキュメント上の例なので、実際のファイルの顔ぶれは違うかもしれません。大事なのは次を押さえることです。
- 認証の解決
- CLI のメニュー / プロバイダーの選択
- 実行時のプロバイダーの解決
- エージェントの実行経路
- provider:model の解釈
- アダプター固有のメッセージ変換があるならそれ
対象を絞ったテストを実行します(各ファイルを別々のサブプロセスで走らせる scripts/run_tests.sh を使ってもかまいません)。
source venv/bin/activate
python -m pytest tests/hermes_cli/test_runtime_provider_resolution.py tests/cli/test_cli_provider_resolution.py tests/hermes_cli/test_setup_model_provider.py tests/run_agent/test_provider_parity.py -qもっと踏み込んだ変更なら、push の前に全体を走らせます。
source venv/bin/activate
python -m pytest tests/ -n0 -q手順 9: 実物での確認
テストのあとは、本物で軽く動かしてみます。
source venv/bin/activate
python -m hermes_cli.main chat -q "Say hello" --provider your-provider --model your-modelメニューを変えたなら、対話式の流れも試してください。
source venv/bin/activate
python -m hermes_cli.main model
python -m hermes_cli.main setupネイティブのプロバイダーでは、ただのテキスト応答だけでなく、ツール呼び出しも最低 1 回は確かめます。
手順 10: 利用者向けドキュメントを更新する
そのプロバイダーを一級の選択肢として出すつもりなら、利用者向けドキュメントも更新します。
website/docs/getting-started/quickstart.mdwebsite/docs/user-guide/configuration.mdwebsite/docs/reference/environment-variables.md
開発者がプロバイダーを完璧に結線しても、利用者が必要な環境変数やセットアップの流れにたどり着けないまま、ということは起こり得ます。
OpenAI 互換プロバイダーのチェックリスト
標準の chat completions のプロバイダーなら、こちらを使ってください。
- [ ]
hermes_cli/auth.pyにProviderConfigを追加した - [ ]
hermes_cli/auth.pyとhermes_cli/models.pyに別名を追加した - [ ]
hermes_cli/models.pyにモデルの一覧を追加した - [ ]
hermes_cli/runtime_provider.pyに実行時の分岐を追加した - [ ]
hermes_cli/main.pyに CLI の結線を追加した(setup.py は自動的に引き継ぎます) - [ ]
agent/auxiliary_client.pyに補助モデルを追加した - [ ]
agent/model_metadata.pyにコンテキスト長を追加した - [ ] 実行時 / CLI のテストを更新した
- [ ] 利用者向けドキュメントを更新した
ネイティブプロバイダーのチェックリスト
新しいプロトコルの経路が要るプロバイダーなら、こちらを使ってください。
- [ ] OpenAI 互換のチェックリストの全項目
- [ ]
agent/<provider>_adapter.pyにアダプターを追加した - [ ]
run_agent.pyで新しいapi_modeに対応した - [ ] 割り込み / 作り直しの経路が動く
- [ ] 使用量と終了理由の取り出しが動く
- [ ] フォールバックの経路が動く
- [ ] アダプターのテストを追加した
- [ ] 実物での軽い動作確認が通る
つまずきやすいところ
1. 認証には追加したのに、モデルの解釈に追加していない
認証情報は正しく解決されるのに、/model や provider:model の入力が失敗します。
2. config["model"] が文字列にも dict にもなり得ることを忘れる
プロバイダー選択のコードの多くは、その両方の形を揃える必要があります。
3. 組み込みプロバイダーが必要だと思い込む
そのサービスが単に OpenAI 互換なだけなら、独自プロバイダーで利用者の問題は解決していて、保守の手間も少なくて済むかもしれません。
4. 補助の経路を忘れる
補助側の振り分けを更新し忘れると、メインのチャットは動くのに要約やメモリの書き出し、画像の補助処理だけが失敗する、ということが起こります。
5. run_agent.py にネイティブプロバイダーの分岐が隠れている
api_mode と self.client. を検索してください。目に付くリクエストの経路が唯一のものだとは考えないでください。
6. OpenRouter だけのつまみを他のプロバイダーへ送る
プロバイダー振り分けのような項目は、それに対応したプロバイダーにだけ渡すべきものです。
7. hermes model は更新したのに hermes setup は更新していない
どちらの流れも、そのプロバイダーを知っている必要があります。
実装中に検索するとよい語
プロバイダーが触れている箇所をすべて洗い出したいときは、次のシンボルを検索してください。
PROVIDER_REGISTRY_PROVIDER_ALIASES_PROVIDER_MODELSresolve_runtime_provider_model_flow_select_provider_and_modelapi_mode_API_KEY_PROVIDER_AUX_MODELSself.client.