ツール検索
MCP サーバーやコア以外のプラグインツールをたくさんセッションに付けていると、 その JSON スキーマだけで毎ターンかなりの割合のコンテキストウィンドウを 使ってしまいます。実際に依頼された内容に関係するツールがほんの数個しか なくても、です。
ツール検索 は、この問題に対して Hermes が用意した、任意で有効にする 段階的開示のしくみです。有効になると、モデルから見えるツール配列の中で MCP ツールとプラグインツールが 3 つの橋渡しツールに置き換わり、モデルは 必要になったツールのスキーマをその都度読み込むようになります。
しくみ
そのターンでツール検索が有効になると、モデルには遅延対象のツールの代わりに 次の 3 つのツールが見えます。
tool_search(queries, limit?) search the deferred-tool catalog (one or more queries)
tool_describe(names) load the full schemas for one or more tools
tool_call(calls) invoke deferred tools; `calls` is an array of {name, arguments}calls には呼び出し 1 回につき 1 件を入れます。1 つだけ呼ぶ場合も、 要素 1 つの配列にします。互いに依存しない呼び出しは、まとめて渡せます(最大 10 件)。 ローカルのツールを含むまとめ呼び出しは、エージェントが 1 件ずつ別々のツール呼び出しに分けます。 そのため各件が、それぞれ範囲の確認、スキーマの検証、フック、承認、結果を個別に持ち、 そのツールの通常の並行実行の方針に従って動きます。connectors__ で始まる名前だけのまとめ呼び出しは、ホスト型のコネクターゲートウェイへ 1 回でまとめて送られます。
やりとりは、だいたい次のような形になります。
Model: tool_search(["create a github issue", "send a slack message"])
→ { results: [ { query: "create a github issue",
matches: ["mcp_github_create_issue", ...] },
{ query: "send a slack message",
matches: ["mcp_slack_post_message", ...] } ],
tools: { mcp_github_create_issue: { description: "...",
required: ["title"], ... },
mcp_slack_post_message: { ... } } }
Model: tool_describe(["mcp_github_create_issue", "mcp_slack_post_message"])
→ { tools: { mcp_github_create_issue: { parameters: { ... } },
mcp_slack_post_message: { parameters: { ... } } } }
Model: tool_call({ calls: [{ name: "mcp_github_create_issue",
arguments: { title: "...", body: "..." } }] })
→ { ok: true, issue_number: 42 }tool_search に渡した各クエリは、同じカタログに対してそれぞれ独立に 検索されます(limit はクエリごとに効きます)。クエリごとのまとまりには ツール名だけが入り、共有の tools マップのほうに、一致した各ツールの説明と 必須パラメーター名が 1 回だけ入ります。クエリは語幹に分解されるので、 「issues」でも create_issue が見つかります。1 件も一致しなかったクエリの まとまりには、つながっているサーバーの一覧が available_sources として 付きます。単に言葉が合わなかっただけなのに、その機能自体が無いと 勘違いしないためです。 tool_describe は、頼まれた名前をすべて 1 回の呼び出しで解決します。 知らない名前は not_found に報告されるだけで、残りの処理は止まりません。
モデルが tool_call を呼ぶと、Hermes は 橋渡しをほどいて、モデルが 直接そのツールを呼んだのとまったく同じように本来のツールへ渡します。 ツール呼び出し前のフック、ガードレール、承認の確認、ツール呼び出し後の フックは、どれも tool_call ではなく本来のツール名に対して動きます。 CLI やゲートウェイの実行履歴の表示も同じようにほどかれるので、橋渡し役では なく本来のツールが見えます。
どんなときに有効になるか
ツール検索は 段階的な開示 を使います。遅延対象のツールが 1 つでもあれば (MCP でもプラグインでも、名前を挙げた組み込みツールでも)橋渡しが有効に なります。カタログの大きさで変わるのは、スキーマを遅らせるかどうかでは なく、カタログのどこまでを見えたままにするかです。
| 段階 | 条件 | モデルから見えるもの |
|---|---|---|
| 0 | 遅延対象のツールが無い | すべてのツールをそのまま読み込み、橋渡しは無し。素通しです。 |
| 1 | 遅延対象カタログの一覧が予算に収まる | 橋渡し + スキル一覧のような形で、遅延対象の全ツールを並べた目録(名前と短い説明。予算を超える場合は名前だけに落とします)。落とし方は サーバー単位 です。大きすぎるサーバー(Cloudflare)と小さなサーバー(Linear)が一緒に付いている場合、小さいほうはツールごとの一覧を保ったまま、大きすぎるサーバーだけが 1 行の要約にまとまります。 |
| 2 | 名前だけにしても、すべてのサーバーのツール一覧が予算を超える(例: Cloudflare の平たい API 面だけで約 3,300 ツール、名前だけで約 32K トークン) | 橋渡しのみ + サーバーごとに 1 行の要約(サーバー名とツール数)。どの領域に手が届くかはモデルに分かります。個々のツールは tool_search からだけたどれます。 |
一覧の予算は min(threshold_pct% of context, listing_max_tokens) です。 この判定はツール配列を組み立てるたびにやり直されるので、セッションの 途中で MCP サーバーを足したり外したりすると、次の組み立てのときに段階が 移ります。
設定
tools:
tool_search:
enabled: auto # auto (default), on, or off
threshold_pct: 5 # listing budget as a percentage of context
search_default_limit: 5
max_search_limit: 25
listing: auto # embed a grouped name+description catalog manifest
listing_max_tokens: 4000
defer: # replace the curated default; [] keeps every tool eager
- computer_use
- session_search
- image_generate
- todo_list
- process_manage
- cronjob_manage
- manage_catalog既定の defer リストには、hermes_cli/config_defaults.py に挙がっている デスクトップ GUI 向けの補助ツールも入っています。出荷時に選ばれている 組み合わせは、このファイルが唯一の正本です。実行時の代替値も同じ値を 使います。
| キー | 既定値 | 意味 |
|---|---|---|
enabled |
auto |
auto/on は、遅延対象のツールが 1 つでもあれば有効になります。off は機能ごと止めます(すべてそのまま読み込みます)。いまのところ auto は on の別名です。将来、スキーマがコンテキストに収まるときは埋め込み、収まらないときだけ遅らせるモードのために予約されています。今日の動きをこの先も確実に保ちたい場合は on か off を書いてください。 |
threshold_pct |
5 |
使っているモデルのコンテキスト長に対する、一覧の予算の割合です。0〜100 の範囲で指定します。 |
search_default_limit |
5 |
モデルが limit を付けずに tool_search を呼んだときに、クエリ 1 件あたり返す候補の数です。 |
max_search_limit |
25 |
モデルが limit で要求できる上限です(クエリごと)。1〜50 の範囲で指定します。 |
listing |
auto |
遅延対象の全ツールをスキル一覧のような形で並べた目録(名前と説明の最初の一文、60 文字以内、MCP サーバーごとにまとめたもの)を、tool_search の説明文に埋め込みます。auto は予算に収まるときだけ埋め込み(収まらなければ名前だけ、さらに段階 2 のサーバー要約へ落とします)、on/off はどちらかに固定します。 |
listing_max_tokens |
4000 |
コンテキストの大きさに関係なく、埋め込む一覧にかける絶対の上限です。200〜60000 の範囲で指定します。大きなカタログは名前だけ、あるいはサーバーごとの要約に落ちますが、完全なスキーマは検索からいつでも取れます。 |
defer |
既定のリスト | 既定で橋渡しに置き換えるツール名です。MCP やプラグインのツールだけでなく、めったに使わない組み込みツールも入れられます。明示的に書くと既定を置き換え、[] にするとすべてのツールで遅延読み込みを止めます。 |
1 回の呼び出しで渡せる配列の上限は、設定ではなく内部の安全弁です。上限を 超えた呼び出しはエラーを返すので、モデルは小さく分けてやり直せます。
一覧がある理由
一覧が無いと、遅延対象の機能は *見えない* ものになります。実際に動かして 測ってみると、モデルは目に見えるコア側のツールで代用したり(遅延対象の GitHub ツールを探さずにターミナルで gh を実行する、など)、tool_search を呼ばずに「そんな機能は無い」と言い切ったりしました。この一覧は、スキルで 使われているやり方をツールに当てはめたものです。どの機能もつねに名前では たどれるようにしておき、パラメーターの完全なスキーマだけを遅らせます。 モデルが一覧の中に目的のツール名をそのまま見つけられれば、tool_search を飛ばして tool_describe に直行でき、1 往復ぶん節約できます。
昔ながらの真偽値の書き方もできます。
tools:
tool_search: true # equivalent to {enabled: auto}コネクター(リモートのツール)
橋渡しは コネクター にも手が届きます。これは、managed tool gateway が提供するリモートのツールです。 ローカルに登録されることはありません。tool_search は各クエリを ゲートウェイにも送り、返ってきた候補を文書としてローカルのカタログに 足します(source: "connectors" の印を付け、 connectors__<connector>__<tool> という名前にします)。そのうえで、 ローカルのものと同じ BM25 の処理、同じ「いちばん珍しい語」の規則で並べます。 limit はまとまり全体に効くので、クエリに答えられるコネクターのツールが、 1 語だけ共通するローカルのツールに押し出されることはありません。 ゲートウェイの呼び出しは 30 秒で打ち切られます。応答が遅い、あるいは 落ちている場合は、ローカルの結果だけに落ちます。tool_describe は コネクターのスキーマをゲートウェイから取りに行き、tool_call は まとめ呼び出しの中のコネクターの項目を、入力の順にひとつずつ別々の ゲートウェイ要求として送ります(ゲートウェイが通常の短縮名では知らない ツール名は、そのままの短縮名でもう一度試すので、1 件で 2 回の要求に なることがあります)。もしあるコネクターが GMAIL_X とそのままの X を 両方出していたら、どちらも connectors__gmail__X になり、実際に動くのは GMAIL_X のほうです。検索はその片方を残してもう片方を落とし、警告を 記録します。結果は数を数え直したうえで、元のまとめ呼び出しの順に戻されます。
tools:
connectors:
enabled: true # false — never touch connector routes; the bridge
# behaves exactly as if the feature didn't existコネクターを使うには Nous の ID が必要です。そのためにサインインする必要は ありません。Nous のアカウントがなければ、最初の manage_connections の操作 (またはデスクトップアプリの Connectors ページを開くこと)で、無料の Nous ID が 自動で作られます。どの画面から使っても同じです。この ID でモデルの提供元が 変わることはありません。Nous の無料枠を同梱したビルド以外では、コネクターと managed tools のためだけに使われます。ID ができるまでは、tool_search は ゲートウェイに何も送らず、結果には「ホスト型のコネクターはまだ設定されて いない」「manage_connections で設定できる」と表示されます。使わないようにする には、次のように設定します。
nous:
guest: falseこの設定にしたとき、あるいはサインイン済みのアカウントに対してゲートウェイが コネクターを提供していないときは、ここまでの話は一切見えません。 ローカルの検索はこのページの他の部分で説明したとおりに動き、モデルには エラーも出ません。
まだひも付けていないアカウントが必要なコネクターを呼ぶと、 CONNECTION_REQUIRED エラーが返ります。manage_connections ツールは、 コネクターとその接続状態を一覧し、認可の手続きを始めます。デスクトップ アプリでは、この呼び出しでカードが表示され、各アプリの接続が済むか 飛ばされるまで待ち、結果を報告します。それ以外の場所では、利用者が開く ための接続リンクをアプリごとに返します。アカウントの接続解除は、利用者が Portal で行います。同じツールはカタログからローカルの MCP サーバーを入れて 有効にし、認可するところまで担当します(mcp: true の対象)。こちらの操作で Nous の ID が作られることはありません。
同じアプリのアカウントを 2 つ以上、それぞれに名前を付けてつなぐこともできます (複数のコネクターアカウント を参照)。 有効なアカウントが 2 つ以上あるコネクターでは、そのツールそれぞれに connector_alias という引数が付き、指定できる値は自分のアカウント名です。 ツールの説明にもその名前が並ぶので(Accounts: home, work.)、tool_search と tool_describe にも表示されます。コネクターの結果の項目には、呼び出しを 実行したアカウントが名前付きなら、その名前が account として付きます。 名前の無いアカウントでは、この項目は付きません。
デスクトップ側のバックエンドが使うアカウント一覧と接続解除の API は、 Portal のアカウント管理サービスを通ります。組織のメンバーシップ確認や 接続解除の監査もそこに含まれます。Portal が使えないときに、ゲートウェイ 直叩きのアカウント管理へ切り替わることはありません。ツールの発見、実行、 接続状態の見張りはゲートウェイ経由で続きます。モデル側のツールから アカウントの接続を解除することはできません。
tool_call はまとめ呼び出しを受け付けます。calls は {name, arguments} の並びで(1 件だけのときも要素 1 つの配列です)、 まとめの中のコネクターの項目は、それぞれ別のゲートウェイ要求として順に 送られます。ローカルの遅延ツールは、1 回の tool_call につき 1 件のままです。 複数件のまとめの中にローカルのツールが混ざっている場合は、呼び出し側が 書いた最初の項目を使って正しい形を示し直す訂正とともに拒否されます。 calls が JSON 文字列として渡された場合も、配列と同じように読み取られます。 承認は項目ごとに送信前に片付き、途中で /stop が入ると、まだ始まって いない項目は送られません(その枠は INTERRUPTED として報告されます)。
使わないほうがよい場合
ツール検索は、毎ターン決まったトークン費用(3 つの橋渡しツールのスキーマと カタログの一覧)と、まだ読み込んでいないツールでの少なくとも 1 往復 (describe → call)を払って、遅延させたスキーマのぶんを節約する取引です。 段階 1 では一覧がすべての機能を見えたままにするので、探すための往復は たいてい無くなり、モデルは tool_describe に直行します。実際に動かして 測ったところ、一覧を出すモードは、すべて読み込む場合と同じだけ仕事を こなしつつ、橋渡しだけの場合より費用が安く済みました。
ツールが少なくて、以前のようにすべて読み込む動きにしたい場合は、 enabled: off にしてください。
消えない引き換え
次の点は、プロンプトキャッシュの整合性を保つという前提から来るものです。 この実装に固有の話ではなく、段階的な開示を行う設計にはどうしても付いて 回ります。
- まだ読み込んでいないツールでは 1 往復増えます。 モデルが遅延対象の
ツールを初めて必要とするとき、スキーマを探して読み込むためにモデルの 呼び出しが 1〜2 回余計にかかります。静的な側でのトークン節約は本物ですが、 その一部は実行時に払い戻す形になります。
- 遅延させたスキーマにはキャッシュの恩恵がありません。 読み込んだ
tool_describe の結果は会話の履歴に入るので、以降のターンではキャッシュ されますが、システムプロンプト側のキャッシュの前置きには決して乗りません。
- 遅延させたスキーマは、プロバイダー側の検証を受けられません。
tool_describe でモデルは遅延対象のツールのスキーマを読めますが、 プロバイダーから見えるのは汎用の tool_call.arguments オブジェクトだけです。 そのため Hermes は、渡す前に本来の引数をローカルで型合わせして検証します。 壊れたスキーマや外部参照など、Hermes が安全に検証できないものについては、 ツール本体や MCP サーバー側が引き続き責任を持ちます。
- モデルの性能に左右されます。 ツール検索は、欲しいツールに対して
モデルがまともな検索クエリを書けることを前提にしています。小さいモデルは これが苦手です。公開されている Anthropic の数字(Opus 4 でツール検索 あり 74%、なし 49%)は伸びしろを示すと同時に、26 ポイントぶんは今なお 取り出しの失敗であることも示しています。
- ツールの増減はキャッシュを無効にします。 セッションの途中でツールを
足したり外したりすると、橋渡しツールの説明文(遅延対象のツール数を含みます) とカタログが変わるので、プロンプトキャッシュが効かなくなります。これは ツールを入れ替えるとき全般に付いて回る話です。
実装の詳細
- 取り出し方: ツール名、提供元の名前(そのツールが属する MCP サーバーや
プラグインのツールセット。おかげで "linear" で検索すれば、ツール名自体に サービス名が入っていなくてもそのサーバーのツールが見つかります)、説明、 パラメーター名を分かち書きしたものに対する BM25 です。索引とクエリの 両方に Snowball の語幹処理(英語)をかけるので、語形が違っても一致します (「issues」で create_issue が見つかります)。あるツールが結果に入るのは、 クエリの中でいちばん珍しい語(いちばん少ないツールの文書にしか出てこない語、 つまり意図を名指しする語です。send や create ではなく gmail、 github、incident のほう)を含むときだけです。いちばん珍しい語が どのツールにも出てこないクエリは、共通語を 1 つ共有するだけのツールを limit 件返す代わりに、つながっている提供元とやり直しのヒントを添えた 空のまとまりを返します。
- 関連度の下限: ツールが候補に出るには、クエリの中の *答えられる* 語
(カタログのどこかに存在する語)の半分以上と一致している必要があります。 長いクエリとたまたま 1 語が重なっただけでは一致とみなしません。存在しない 機能を探した場合は、それらしく見える一覧を返してモデルが延々と言い換えを 続ける代わりに、結果なしを返します。この下限が効き始めるのは答えられる語が 4 つ以上のときなので、「list issues」のような短いクエリは取りこぼしなく 返りますし、ツール名そのものを打ったクエリはつねに一致します。
- 並列実行は橋渡しをほどいて判断します。 まとめ実行の計画は、
tool_call
という見かけの名前ではなく、その下にある本来のツールを見て同時実行の 可否を決めます。そのため supports_parallel_tool_calls: true を入れた MCP サーバーは、橋渡し越しに呼ばれても同時実行のままですし、 tool_search と tool_describe の問い合わせも、他の読み取り専用ツールと 同じようにまとめて並行で動きます。
- カタログはターンをまたいで状態を持ちません。 組み立てのたびに現在の
ツール定義の一覧から作り直します。セッションをキーにした Map は ありません。保存したカタログが実際のツール登録内容とずれていく類の不具合を、 これで避けています。
- カタログはセッションのツールセットの範囲に閉じています。
tool_search、
tool_describe、tool_call が見て呼べるのは、そのセッションに実際に 許可されたツールだけです。ツールセットの一部だけを与えられたサブ エージェント、かんばんのワーカー、ゲートウェイのセッションは、橋渡しを 使ってもその範囲の外にあるツールを見つけたり呼んだりはできません。 遅延対象のカタログは、そのセッション自身で有効・無効になっている ツールセットのうち遅延できるぶんであって、プロセス全体の登録内容では ありません。
- JS のサンドボックスはありません。 Hermes は、より単純な「構造化ツール」
方式(search / describe / call をただの関数として扱う方式)を使っています。 他の実装が用意している JS サンドボックスの「コードモード」は面が広すぎる ので、採用していません。
関連項目
tools/tool_search.py— 実装tests/tools/test_tool_search.py— 回帰テスト一式- 設計のもとになった調査については、最初の実装 PR にある
openclaw-tool-search-report の PDF