3行でいうと
- 構造化出力は、JSONスキーマや文法で、生成の途中に出せる文字を制限する方式です。形式は守らせやすいですが、中身の正しさは別の話です。
- Ollamaは
formatにスキーマを渡し、llama.cppはjson_schemaや GBNF 文法、LM Studioはresponse_formatを使います。 - ツール呼び出しは、モデルが「この関数をこの引数で呼んで」と書き出すだけで、実行するのは自分のコードです。
- 論文では、形式の制限が強いほど、推論の問題の成績が下がる傾向が報告されています。
この記事は、実際に使って確かめた記録ではありません。この記事は公式の文書や論文を読んでまとめたものです。
先に結論
機械が読む形(JSON)でAIの答えを受け取りたいときは、「お願いする」だけでなく、「生成の途中で、形式に外れる文字を選べないようにする」方式が使えます。これがローカルの道具に共通する考え方です。
見立てです。使い分けは次のようになると思います。
- 項目が決まった抽出(人名、日付、分類):JSONスキーマを渡す。
- JSONでは表せない独自の形式:llama.cppのGBNF文法。
- 外のデータを引く、計算する、処理を動かす:ツール呼び出し。ただし、モデルがツール用の書式を学習しているかどうかに左右されます。
前提:用語
- 構造化出力は、AIの出力を、決まった形式(JSONなど)に従わせることです。
- JSON Schemaは、JSONの形(項目名、型、必須かどうか)を書く標準的な書き方です。
- サンプリングは、モデルが次の文字(トークン)を選ぶ段階です。この段階で、文法に合わない候補を除く方式を、文法に基づくサンプリングと呼びます(llama.cppの文書の用語)。
- GBNFは、llama.cppで使う文法の書き方です。BNF(文法を書く記法)に、正規表現のような書き方を足したものです。
- ツール呼び出し(関数呼び出し)は、モデルが関数名と引数を書き出し、プログラムがそれを実行して、結果をモデルに戻す仕組みです。
- チャットテンプレートは、会話や道具の定義を、モデルが学習した書式に並べ直す雛形です。
道具ごとの対応
| 道具 | 構造化出力 | ツール呼び出し |
|---|---|---|
| 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)
文書の注意点です。
- スキーマは、プロンプトにも文字列として入れるのが理想的、と書かれています。
- 温度(出力のばらつき)を0にすると、より決まった結果になる、と書かれています。
- 結果は文字列で返るので、Pydanticなどで検証して読み込みます。
- 画像を読めるモデルにも、同じ
formatが使えます。
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つに分けています。
- ネイティブ:Llama 3.1/3.3、Qwen 2.5、Mistral Nemo、Hermes 2/3、GPT-OSSなど、書式を知っている型。
- ジェネリック:テンプレートが既知でないときの代替。「ネイティブより多くのトークンを使い、効率が悪いかもしれない」と書かれています。
使うには、ツール対応のテンプレートが要ります。文書は、/props で chat_template などを確かめるよう案内しています。--jinja は、サーバーのREADMEでは既定で有効(default: enabled)と書かれています。一方、関数呼び出しの文書は、--jinja を付けて起動する例を載せています。
LM Studioも、モデルを「ネイティブ」と「デフォルト」に分けています。ネイティブは、ツール対応のチャットテンプレートがあり、LM Studioがその書式を解釈できる場合です。それ以外は、LM Studioが独自のシステムプロンプトと書式を足して、ツール呼び出しを代用します。小さいモデルは、書式が崩れて、tool_calls に解釈できず、本文にそのまま出ることがある、と書かれています。
注意書き
- llama.cppの文書は、KVキャッシュの極端な量子化(例:
-ctk q4_0)が、ツール呼び出しの性能を大きく下げうる、と警告しています。 - Ollamaの文書は、「単発の呼び出し」の例で、「1つのツール呼び出ししか返さないモデル向けの書き方」と注記しています。複数を返すモデルでは、全部を処理する書き方が別にあります。
- Ollamaの互換APIは、
toolsに対応する一方で、tool_choice(呼ぶ関数を強制する指定)は対応しない項目の側に入っています。
品質への影響
LM Studioの文書は、「すべてのモデルが構造化出力に向くわけではなく、特に7B未満のモデルで難しい」と書いています。確かめるには、モデルカードを見るよう案内しています。
論文(Tamら、2024年、arXiv:2408.02442)は、JSONやXMLなどの形式の制限が、モデルの能力に影響するかを調べ、形式を制限すると推論の成績が大きく下がり、制約が厳しいほど下がり方が大きい傾向がある、と報告しています。
見立てです。この結果は、特定のモデルと課題での報告なので、手元のモデルに当てはまるかは分かりません。ただ、「考えさせてから答えさせる」ような課題では、先に自由な文章で考えさせ、最後に形式に直す二段階に分けるほうが安全かもしれない、と考えています。確かめてはいません。
選び方の目安
見立てです。
- まず、スキーマで試す:項目が決まった抽出は、スキーマで足ります。結果は必ずプログラムで検証してください(形式が合っても、値が正しいとは限りません)。
- 文法は、JSONで書けない形式のときだけ:GBNFは強力ですが、遅くなる落とし穴が文書に書かれています。
- ツール呼び出しは、モデルを選ぶ:ネイティブ対応と書かれたモデルを使い、小さいモデルでは、失敗時の処理(再試行)を用意します。
- KVキャッシュは量子化しすぎない:上の警告のとおりです。
よくあるつまずき
- スキーマをプロンプトに書いていない:llama.cppでは、モデルがスキーマを見ていません。項目の意味を、プロンプトで説明します。
- 未対応の機能が黙って無視される:llama.cppのスキーマ変換は、未対応の機能を飛ばします。想定した制約が効いているか、変換後の文法で確認します。
- 値の範囲を守らせたつもりが、守られていない:
numberの最小・最大は、変換で未対応と書かれています。 - ツール呼び出しが本文に出る:書式が解釈されなかったときの動きです(LM Studio)。テンプレートを確認します。
- 思考するモデルとの組み合わせ:ここは文書での確認が取れませんでした。
気をつけること
- この記事では、ここで紹介した設定をどれも実行していません。実際にどのモデルで動くか、速度への影響は確認できていません。
- 構造化出力は、形式を保証する仕組みで、内容の正しさは保証しません。
- llama.cppとLM Studioの文書は、どちらも更新が頻繁です。llama.cppはmasterブランチを2026年10月8日に読みました。LM Studioの「ネイティブ対応モデル」の一覧は、文書自身が「変わりうる」と書いており、載っているのはQwen2.5、Llama 3.1/3.2、Ministralなどです。
- Ollamaが構造化出力をどう実現しているか、スキーマのどの機能に対応するかは、文書に書かれておらず、確認できていません。
- 論文の結果は、論文が使った課題とモデルのものです。ほかの論文では異なる結果もありえます。
出典
- Structured Outputs(Ollama docs、Ollama、2026年10月8日確認)
- Tool calling(Ollama docs、Ollama、2026年10月8日確認)
- OpenAI compatibility(Ollama docs、Ollama、2026年10月8日確認)
- Structured outputs(Ollama blog、Ollama、2026年10月8日確認)
- GBNF Guide(llama.cpp、ggml-org、2026年10月8日確認)
- Function Calling(llama.cpp、ggml-org、2026年10月8日確認)
- llama-server README(llama.cpp、ggml-org、2026年10月8日確認)
- Structured Output(LM Studio Developer Docs、LM Studio、2026年10月8日確認)
- Tools and Function Calling(LM Studio Developer Docs、LM Studio、2026年10月8日確認)
- Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models(arXiv:2408.02442、Tamら、2026年10月8日確認)
- Efficient Guided Generation for Large Language Models(arXiv:2307.09702、Willard・Louf、2026年10月8日確認)
更新履歴
- 2026年10月25日 書き方を直しました。
- 前:運営者が使っていないと読める書き方 → いま:「この記事は、実際に使って確かめた記録ではありません」。公式の資料を読んでまとめた記事で、使い心地は書いていない、という意味に直した
