この記事は後半です。前半は Qiita にあります: Kimiの92%を100%に押し上げたvLLMのスイッチは何を変えたのか

画像: arxiv.org より
vLLM の --tool-strict-level は、単なる「厳格モード」ではない。公式ドキュメントでは、structural-tag ベースの tool calling に対する server-side floor と説明されている。つまり、リクエストごとの気分ではなく、サーバー側で最低限の強制線を引くための設定だ。
auto は、クライアントの tool_choice と各 tool の strict に従う。ここでは、リクエストが弱ければサーバーも弱いままだ。function に上げると、tool 呼び出しの外枠そのものが固定される。markup と function 名を毎回守らせるので、「tool call っぽい何か」が混ざる余地が減る。さらに parameter では、引数スキーマまで拘束する。ここまで来ると、壊れやすいのは本文ではなく、JSON の型や必須項目のほうになる。
この差は地味だが大きい。ツール呼び出しの失敗は、単に出力が少し崩れるのではなく、後続のエージェントがそもそも呼び出しとして認識できなくなる失敗だ。だから外枠だけでも固定すると、少なくとも「呼び出しなのか、ただの文章なのか」という初歩の取り違えが減る。引数まで縛れば、後段の schema validation で落ちる確率も下がる。
構造化生成の強制は、ただのパース処理ではない。XGrammar の解説が示す通り、constrained decoding は生成時に許されないトークンを logits から落とす。JSON のような単純な形式でも、次に来てよい文字列を都度判断しなければならない。一般の CFG を扱うと、なおさら重い。
MLC の XGrammar 記事では、こうした方式は柔軟だが、全語彙を毎回チェックするコストが無視できないと説明されている。そこで context-independent な部分を先にまとめ、context-dependent な部分だけを実行時に見る。arXiv の論文要約でも、語彙を分けて事前チェックし、persistent stack と推論エンジンとの協調でオーバーヘッドを抑えるとされている。要するに、100% に近い形保証を実現するには、かなりの工夫が要る。
vLLM のサーバー側スイッチは、その処理を「どこで、どの範囲で、有効にするか」を定めるものだ。無条件に全部を縛るのではなく、auto、function、parameter の段階を切っているのは、Ubicloud の記事によれば、モデルによっては縛ること自体が不具合を招き、汎用エンジンの既定値として全部を縛るのは危ないからだ。逆に言えば、何も考えずに最強の制約を入れればよい、という話ではない。
この手の設定は、研究用途よりも運用で効く。たとえば社内エージェントがチケットを起票する、監視結果を要約して定型アクションを呼ぶ、あるいは検索結果を踏まえて別 API を叩く。こういう場面では、出力の自然さより、毎回同じ型で返ることのほうが重要だ。モデルが少し気の利いた文章を返すことより、正しいツール名と正しい引数が安定して出ることのほうが価値がある。
一方で、すべての場面で parameter まで固定する必要はない。会話の途中で柔らかく誘導したいだけなら、外枠の安定だけでも十分なことがある。私はまず function で様子を見る。そこで schema failure が残るなら parameter に上げる。いきなり最厳格にせず、壊れ方を見て段階を上げるほうが運用しやすい。
それと、モデル側の前提も無視できない。Kimi 系では、vLLM のレシピや MoonshotAI の案内でも、tool_call_parser や reasoning_parser の指定、tool-call ID の扱い、最新版への更新が前提になっている。K2-Vendor-Verifier がベンダー間の schema accuracy を並べているのも、同じモデル名でも実装差がそのまま結果差になるからだ。つまり「モデルを変えずに直す」余地は、意外とサーバー側にある。
92% から 100% への差は、派手な改良ではなく、境界線の引き直しで生まれる。どこまでをモデル任せにして、どこからをサーバーで縛るか。その設計をきちんと持つと、同じ重みでも出力は別物になる。私はまず、その差をベンチマークではなく運用の失敗率で見る。そこが本当に痛い場所だからだ。