TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

ツール呼び出し:パーサーがテキストを関数呼び出しに変換し、strictが引数を制限する

ステータス:ドラフト(2026-10-27)、実測値。 測定は8GBクラスのノートPC用GPU(Ada世代)1枚、vLLM 0.31.0、Qwen3-Reranker-0.6B(リランキング用にファインチューニングされた Qwen3 0.6B 因果言語モデル)を使用して行いました。公式ドキュメントは0.32.0(2026-10-18リリース)を基準に2026-10-27時点のものを確認し、0.31.0のドキュメントと異なる箇所は別途記載しています。 2026-10-27追加。 会話用の Qwen3-0.6B、Qwen3-0.6B-GPTQ-Int8、Qwen3-4B-AWQ と vLLM 0.32.0 を用いて再測定した結果をセクション3-1に追加しました。0.31.0で測定した前述の数値はそのまま残しています。 表記ルール:実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表記します。

目次

1. ツール呼び出しは決まった形式のテキストである

エージェントが天気APIを呼び出す場面を想像してみましょう。モデルの内部に「関数呼び出し」という特別な出力装置が存在するわけではありません。モデルはこれまで通りトークンを1つずつ生成しているのであり、単に学習時に「ツールを呼び出すときはこのような形式で書く」ことを学習したに過ぎません。Qwen系はHermes形式を使用します。

<tool_call>
{"name": "get_weather", "arguments": {"city": "Seoul", "unit": "c"}}
</tool_call>

サーバーが行う処理は次の3つです。

  1. ツールの一覧をプロンプトに挿入します。 リクエストの tools を、モデルのチャットテンプレートがシステムプロンプト内にテキストとして書き込みます。
  2. モデルが生成したテキストを選択します。 生成されたテキストから上記の形式を見つけ出し、OpenAI形式の message.tool_calls に変換するのが ツールパーサーです。モデルによって形式が異なるため、パーサーも hermes, llama3_json, mistral, qwen3_xml のように複数存在します。
  3. 必要に応じて形式を強制します。 tool_choice が required であるか特定の関数名である場合、第4回の構造化出力によってトークンレベルで呼び出しの形式が強制されます。

有効化するオプションは次の通りです。 --enable-auto-tool-choice がないと tool_choice="auto" リクエストを受け付けません。

vllm serve <모델> --enable-auto-tool-choice --tool-call-parser hermes

2. 4種類の tool_choice

tool_choice 呼び出しの義務 形式を保証するもの
auto なし、モデルが決定 パーサーのみ。モデルが形式を間違えた場合、テキストとして残るか欠落する
required 1つ以上が必須 構造化出力が形式を強制
名前を指定({"type":"function","function":{"name":...}}) その関数のみ 構造化出力が形式を強制
none 呼び出さない ツール定義はデフォルトでプロンプトに残る(--exclude-tools-when-tool-choice-none で除外)

公式ドキュメントには、名前を指定した呼び出しについて「パース可能な呼び出しは保証するが、質の高い呼び出しは保証しない」と記載されています。この一文が、本記事の実測結果をそのまま要約しています。

形式と引数は別々に制限される

0.21.0以降、xgrammar の 構造タグ(structural tag) によってツール呼び出しを強制する機能が導入され、現在のドキュメントでは強制の仕組みが2段階に分けられています。

サーバー側のデフォルト値 --tool-strict-level は auto(デフォルト、リクエストに従う)、 function(すべてのリクエストでエンベロープを強制)、 parameter(すべてのツールを strict のように扱う)の3つです。ほとんどのエージェントフレームワークは strict を付与しないため、オペレーターがデフォルトを引き上げられるように作られています。環境変数 VLLM_ENFORCE_STRICT_TOOL_CALLING=false は構造タグを丸ごと無効化します。

3. 実際に測定してみる

環境と方法

結果

tool_choice strict tool_calls が出力された回数 スキーマに準拠した引数 応答時間(中央値)
auto なし 0/5 0/5 24.5ms
auto あり 0/5 0/5 24.6ms
required なし 4/5 0/5 216.6ms
名前を指定 なし 4/5 0/5 216.2ms
required あり 5/5 5/5 263.1ms
名前を指定 あり 5/5 5/5 261.7ms

生データの応答の一部をそのまま転記します。

同じシードにおいて required と名前指定の出力は文字単位で完全に一致していました。ツールが1つだけの場合、両方式とも同じ文法を生成するためです。

サーバー側もこれをカウントしています。テスト終了後、 /metrics の vllm:tool_call_parser_invocations_total は、非ストリーミングのチャットリクエストにおいて outcome="tool_call" 18, outcome="no_tool_call" 12 でした。クライアント側で計測した値(tool_calls が出力された応答18個、出力されなかった応答12個)と一致しています。

読み方

  1. strict のない required は「呼び出しが存在する」ことだけを保証しました。 関数名までは合致していましたが、引数が空のオブジェクトであったり必須フィールドが欠落していました。0.31.0および0.32.0のどちらのドキュメントでも「strict のないツールは required や名前指定でも緩い引数の文法しか受け付けない」と記載されており、実測結果はその記述と一致していました。
  2. strict を有効にするとスキーマは満たされるものの、意味合いは正しくなりませんでした。 すべての引数がJSONとして読み込まれ、必須フィールドや列挙値は遵守されましたが、質問にある Seoul は一度も出現せず、 "none" のような値も出力されました。文法は「許可されたトークン」を制限するだけであり、残されたトークンの中からどれを選ぶかは依然としてモデルが決定します。
  3. 時間の差分は大部分がトークン数の違いによるものです。 strict 側が50msほど長くなりましたが、 {} の代わりに2つのフィールドを両方記述したことでトークンが増えた分が混入しているため、文法チェック自体のコストとして捉えてはなりません。文法コスト単体の測定値は第4回に掲載されています。

3-1. 2026-10-27再測定:ツールを理解するモデルと0.32.0

これまでのテストでは、ツールを理解しないモデルを使用して「サーバーがサポートできる範囲」を確認しました。今回は、ツール呼び出しを学習済みのモデルを使用して、内容まで検証します。

モデル tool_choice strict 呼び出しが出力された回数 正しい都市・単位 応答時間の中央値
Qwen3-0.6B auto なし 6/6 6/6 162.1ms
Qwen3-0.6B required なし 6/6 6/6 178.0ms
Qwen3-0.6B 名前を指定 なし 6/6 6/6 178.1ms
Qwen3-0.6B auto・required・名前指定 あり 18/18 18/18 184.1〜185.4ms
Qwen3-0.6B GPTQ 8ビット 6つのケースすべて なし・あり 36/36 36/36 115.7〜140.6ms
Qwen3-4B AWQ 4ビット 6つのケースすべて なし・あり 36/36 36/36 338.7〜362.4ms

このテストはツールが1つ、短い質問が1つというシンプルなものです。複数のツールがある場合や引数が複雑な場合の精度については、未測定のままです。

4. 運用への示唆

5. 確認できていない事項

参考

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます