3行でいうと

  • 構造化出力は、JSONスキーマや文法で、生成の途中に出せる文字を制限する方式です。形式は守らせやすいですが、中身の正しさは別の話です。
  • Ollamaは format にスキーマを渡し、llama.cppは json_schema や GBNF 文法、LM Studioは response_format を使います。
  • ツール呼び出しは、モデルが「この関数をこの引数で呼んで」と書き出すだけで、実行するのは自分のコードです。
  • 論文では、形式の制限が強いほど、推論の問題の成績が下がる傾向が報告されています。

この記事は、実際に使って確かめた記録ではありません。この記事は公式の文書や論文を読んでまとめたものです。

先に結論

機械が読む形(JSON)でAIの答えを受け取りたいときは、「お願いする」だけでなく、「生成の途中で、形式に外れる文字を選べないようにする」方式が使えます。これがローカルの道具に共通する考え方です。

見立てです。使い分けは次のようになると思います。

前提:用語

道具ごとの対応

道具 構造化出力 ツール呼び出し
Ollama format にJSONスキーマ(または "json")を渡す。OpenAI互換の response_format も可 tools に関数を渡す。OpenAI互換でも可(tool_choice は未対応)
llama.cpp(llama-server) json_schema、response_format、grammar --jinja と、ツール用のテンプレートが必要
LM Studio /v1/chat/completions の response_format tools。モデルにより、ネイティブ対応とデフォルト対応がある

Ollamaの文書には、「Ollama Cloudは構造化出力にまだ対応していない」とあります。仕組み(文法か、別の方法か)は、読んだ文書には書かれていませんでした。

LM Studioの文書は、構造化出力のエンジンを、GGUFモデルではllama.cppの文法に基づくサンプリング、MLXモデルではOutlines(構造化生成のライブラリ)だと書いています。Outlinesの考え方は、論文(Willard・Louf、2023年、arXiv:2307.09702)で、文章の生成を有限状態機械の遷移として捉え、正規表現や文脈自由文法に合う文字列だけを選ばせる方法として説明されています。

Ollamaでスキーマを渡す

公式文書の例(短縮)です。この記事では、実行していません。

from ollama import chat
from pydantic import BaseModel

class Country(BaseModel):
  name: str
  capital: str
  languages: list[str]

response = chat(model='gpt-oss',
  messages=[{'role': 'user', 'content': 'Tell me about Canada.'}],
  format=Country.model_json_schema())
country = Country.model_validate_json(response.message.content)

文書の注意点です。

llama.cppのスキーマとGBNF

llama.cppの文書によると、JSON Schemaの一部をGBNFに変換して使います。llama-server では、json_schema の欄、または /chat/completions の response_format、コマンドラインでは --json(-j)で渡します。文法を直接書くなら、grammar(サーバー)か --grammar-file(コマンドライン)です。

文書の注意書きに、重要な点があります。スキーマは出力を制限するだけで、プロンプトには入りません。モデルはスキーマを見ていないので、期待する形は、プロンプトで説明する必要があります。ただし、ツール呼び出しでは、スキーマがプロンプトに入ります。

GBNFの書き方と、JSON Schemaの未対応の機能

GBNFの基本

文法は「規則名 ::= 並び」の形で書きます。root が出力全体の形です。文書の例(箇条書きの文法)です。

root ::= ("- " item)+
item ::= [^\n]+ "\n"

[^\n] は改行以外の1文字、+ は1回以上の繰り返し、| は選択肢、? は省略可能です。{m,n} で繰り返し回数を指定でき、文書は、x? x? x? のように並べると遅くなるので x{0,N} を使うよう勧めています。ひらがなの範囲 [ぁ-ゟ] のように、Unicodeも書けます。特定のトークンを指定する書き方もあります。

スキーマの変換で、文書が挙げている制約

  • additionalProperties の既定は false(速く、作り話を減らすため)。
  • 未対応の機能は、黙って飛ばされる。変換のコマンドで警告を確認することが勧められている。
  • properties と anyOf / oneOf を同じ型に混ぜられない。
  • prefixItems は動かない(items は動く)。
  • minimum、maximum などは integer のみ(number は不可)。
  • 入れ子の $ref は動かない。pattern は ^ で始まり $ で終わる必要がある。
  • uniqueItems、contains、not、if / then / else などは、対応する予定がないとされている。

文書のスキーマ例

文書は、名前(1〜100文字)と年齢(0〜150の整数)を持つオブジェクトの配列(10〜100件)をスキーマで書き、それがどんな文法に変換されるかを示しています。文字数や数の範囲も、文法として表せます。

ツール呼び出し

仕組みは、LM Studioの文書の説明が分かりやすいです。モデルは、呼びたい関数と引数を文章として出力するだけで、直接は実行できません。プログラムがその出力を読み取り、関数を実行し、結果を次のメッセージとしてモデルに返します。Ollamaの文書にも、この流れ(ツール呼び出し、結果の返送、最終の返答)の例があり、複数の関数を同時に呼ぶ「並列呼び出し」や、繰り返しの「エージェントのループ」の例もあります。

書式の問題

モデルごとに、ツールを書く書式が違います。llama.cppの文書は、llama-server --jinja で使う関数呼び出しを、2つに分けています。

使うには、ツール対応のテンプレートが要ります。文書は、/props で chat_template などを確かめるよう案内しています。--jinja は、サーバーのREADMEでは既定で有効(default: enabled)と書かれています。一方、関数呼び出しの文書は、--jinja を付けて起動する例を載せています。

LM Studioも、モデルを「ネイティブ」と「デフォルト」に分けています。ネイティブは、ツール対応のチャットテンプレートがあり、LM Studioがその書式を解釈できる場合です。それ以外は、LM Studioが独自のシステムプロンプトと書式を足して、ツール呼び出しを代用します。小さいモデルは、書式が崩れて、tool_calls に解釈できず、本文にそのまま出ることがある、と書かれています。

注意書き

品質への影響

LM Studioの文書は、「すべてのモデルが構造化出力に向くわけではなく、特に7B未満のモデルで難しい」と書いています。確かめるには、モデルカードを見るよう案内しています。

論文(Tamら、2024年、arXiv:2408.02442)は、JSONやXMLなどの形式の制限が、モデルの能力に影響するかを調べ、形式を制限すると推論の成績が大きく下がり、制約が厳しいほど下がり方が大きい傾向がある、と報告しています。

見立てです。この結果は、特定のモデルと課題での報告なので、手元のモデルに当てはまるかは分かりません。ただ、「考えさせてから答えさせる」ような課題では、先に自由な文章で考えさせ、最後に形式に直す二段階に分けるほうが安全かもしれない、と考えています。確かめてはいません。

選び方の目安

見立てです。

  1. まず、スキーマで試す:項目が決まった抽出は、スキーマで足ります。結果は必ずプログラムで検証してください(形式が合っても、値が正しいとは限りません)。
  2. 文法は、JSONで書けない形式のときだけ:GBNFは強力ですが、遅くなる落とし穴が文書に書かれています。
  3. ツール呼び出しは、モデルを選ぶ:ネイティブ対応と書かれたモデルを使い、小さいモデルでは、失敗時の処理(再試行)を用意します。
  4. KVキャッシュは量子化しすぎない:上の警告のとおりです。

よくあるつまずき

気をつけること

出典

更新履歴