「"JSON形式で返して"とプロンプトに書いても、マークダウンの```json```囲みが付いてしまいそのままパースできない」
そんな悩みを抱えるエンジニアは多いはずです。この記事では、Ollamaが提供するJSON modeの仕組みと設定方法を、curl・Python両方の実装手順で解説します。
APIパラメータの設定から、システムプロンプトによるスキーマ定義、JSONDecodeError発生時のリトライ設計まで、業務システムに組み込む上で必要な知識を一通りカバーしています。
この記事のポイント
・"format": "json" をAPIリクエストに追加するだけでJSON modeが有効になる
・スキーマの固定はシステムプロンプトで行う。モデル側では型・フィールド名を保証しない
・json.loads(response.json()["message"]["content"]) でPythonからパースできる
・本番運用ではjsonschemaバリデーション+リトライロジックを必ずセットで実装する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
ローカルLLMの出力バラつき問題とJSON modeの役割
LLMにデータ抽出や分類を依頼するとき、出力形式の不安定さは大きな障壁になります。同じプロンプトでも、実行するたびに「はい、以下がJSON形式です:」という前置きが付いたり、Markdownのコードフェンス(```json ... ```)で囲まれたりと、プログラムで扱いにくい形式が返ることがあります。これはモデルが「人間に読みやすいように」説明を付けようとする性質を持つためです。プロンプトで「JSONのみ返してください」と指示しても、指示に完全に従わないケースが生まれます。
OllamaのJSON modeは、この問題をモデル呼び出し時のAPIパラメータで解決します。
format: "json" を指定すると、llama.cppのグラマー制約(grammar-constrained decoding)が有効になり、モデルの出力が文法的に正しいJSONのみに強制されます。前置き文や```囲みは物理的に生成できなくなります。ただし、JSON modeは「出力がJSONとして有効か」を保証するものです。特定のスキーマに従うかどうかはプロンプト設計の仕事になります。フィールド名や型の制御は、次節で説明するシステムプロンプトの書き方が鍵です。
なお、OllamaのインストールやUbuntu Serverへの基本的なセットアップ方法は Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド で解説しています。まだ環境が整っていない方はそちらを先に確認してください。
JSON modeを有効にするAPI仕様
OllamaのJSON modeは、APIリクエストのJSONボディに"format": "json" を追加するだけで有効になります。対応しているエンドポイントは /api/generate(単発生成)と /api/chat(会話形式)の両方です。stream パラメータは false に設定するのが業務利用での基本です。デフォルトの stream: true のままだと、出力がチャンクごとに返ってくるためJSON全体を組み立てる処理が別途必要になります。バックエンドAPIとして組み込む場合は false の方がシンプルに扱えます。設定するパラメータをまとめると以下の通りです。
・
"format": "json" — JSON modeを有効化する必須パラメータ・
"stream": false — レスポンスを一括受け取りにする・
"temperature": 0 — 出力を安定させたい場合に低めに設定する(省略可)・
"num_predict": 2048 — 複雑なJSONが途中で切れる場合に出力トークン上限を緩めるcurlコマンドでJSON modeの動作を確認する手順
1. /api/generateエンドポイントで確認する
Ollamaサーバーが起動している状態で以下のコマンドを実行します。モデル名は環境に合わせて変更してください。# /api/generateでJSON modeを有効にしたリクエスト例 curl http://localhost:11434/api/generate \ -H "Content-Type: application/json" \ -d '{ "model": "llama3.3:70b-instruct-q4_0", "prompt": "名前:田中太郎、部署:営業部、入社年:2018年 この情報をJSONで返してください", "format": "json", "stream": false }'
response フィールドに格納されます。
# レスポンス例(整形済み表示) { "model": "llama3.3:70b-instruct-q4_0", "response": "{\"name\": \"田中太郎\", \"department\": \"営業部\", \"year_joined\": 2018}", "done": true }
response の値が```囲みや前置き文なしの純粋なJSON文字列になっていることを確認します。この形式であれば json.loads() でそのままパースできます。もし
response に説明文が含まれている場合、format: "json" の指定が反映されていない可能性があります。OllamaのバージョンをアップデートするかモデルをPullし直してから再実行してください。
2. /api/chatエンドポイントで確認する
会話形式のAPIを使う場合は/api/chat エンドポイントを使います。systemロールでスキーマを定義し、userロールでデータを渡すパターンが標準的です。
# /api/chatでシステムプロンプトと組み合わせたJSON modeの例 curl http://localhost:11434/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "llama3.3:70b-instruct-q4_0", "messages": [ { "role": "system", "content": "あなたはデータ抽出AIです。入力テキストから情報を抽出し、必ず以下のJSONスキーマのみで返してください。{\"name\": \"string\", \"department\": \"string\", \"year_joined\": \"number\"}" }, { "role": "user", "content": "名前:田中太郎、部署:営業部、入社年:2018年" } ], "format": "json", "stream": false }'
/api/chat のレスポンスはJSON文字列が message.content フィールドに格納されます。Pythonでは response.json()["message"]["content"] で取り出してからパースします。
システムプロンプトでJSONスキーマを定義する方法
1. スキーマをシステムプロンプトに明示する
JSON modeは「出力がJSONである」ことしか保証しません。フィールド名や型を固定するには、システムプロンプトに期待するスキーマを直接書き込む必要があります。効果的な書き方のポイントは3つです。
・「以下のJSONスキーマのみで返してください」という強い指示を使う
・フィールド名と型を明示する(例:
"count": number)・「それ以外の文章は一切含めないこと」と付け加える
スキーマ定義の記述例を示します。
# システムプロンプトのスキーマ定義例(問い合わせ分類用) あなたはサポート問い合わせ分類AIです。 入力されたメール本文を分析し、必ず以下のJSONのみで返してください。 それ以外の文章・説明は一切出力しないこと。 { "category": "billing" | "technical" | "general", "priority": "high" | "medium" | "low", "summary": "50文字以内の要約", "requires_escalation": true | false }
| で列挙することでモデルが候補を正しく認識します。true | false で型を示すことも有効です。
2. Few-shot例で精度を高める
スキーマ定義だけでは出力が不安定な場合、Few-shot(入出力の例示)を追加すると大幅に安定します。特にenumフィールドはFew-shotとの組み合わせが有効です。システムプロンプトに以下のような例示セクションを追加します。
# Few-shot例(システムプロンプトに追記する部分) 【入力例】 「先月の請求書の金額が通知と違っています」 【出力例】 {"category": "billing", "priority": "high", "summary": "請求金額の誤りに関する問い合わせ", "requires_escalation": true} 【入力例】 「パスワードのリセット方法を教えてください」 【出力例】 {"category": "technical", "priority": "low", "summary": "パスワードリセット方法の問い合わせ", "requires_escalation": false}
small model(7B以下)に複雑なスキーマを使う場合は、Few-shotなしではほぼ機能しないと考えてください。この場合はFew-shotを2~3ペア用意することを推奨します。
PythonでJSON出力を業務処理に組み込む実装手順
1. requestsライブラリで実装する
社内での業務自動化スクリプトにOllamaのJSON modeを組み込む場合、Pythonのrequests ライブラリを使ったシンプルな実装が最もメンテナンスしやすいです。Ollamaをインストール済みのUbuntu Serverで動作確認済みの実装例を示します。
import requests import json OLLAMA_URL = "http://localhost:11434/api/chat" MODEL = "llama3.3:70b-instruct-q4_0" SYSTEM_PROMPT = """あなたはサポート問い合わせ分類AIです。 入力されたメール本文を分析し、必ず以下のJSONのみで返してください。 {"category": "billing" | "technical" | "general", "priority": "high" | "medium" | "low", "summary": "50文字以内の要約", "requires_escalation": true | false}""" def classify_email(email_body: str) -> dict: payload = { "model": MODEL, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": email_body} ], "format": "json", "stream": False } response = requests.post(OLLAMA_URL, json=payload, timeout=60) response.raise_for_status() content = response.json()["message"]["content"] return json.loads(content) # 実行例 result = classify_email("先月の請求書の金額が通知と違います。至急確認をお願いします。") print(result) # {"category": "billing", "priority": "high", "summary": "請求金額の誤りに関する問い合わせ", "requires_escalation": true}
2. JSONスキーマバリデーションを追加する
業務システムでは、モデルが正しいスキーマを返したかを確認するバリデーションを追加します。jsonschema ライブラリを使うと、フィールドの存在と型を一括チェックできます。
# pip install jsonschema from jsonschema import validate, ValidationError SCHEMA = { "type": "object", "required": ["category", "priority", "summary", "requires_escalation"], "properties": { "category": {"type": "string", "enum": ["billing", "technical", "general"]}, "priority": {"type": "string", "enum": ["high", "medium", "low"]}, "summary": {"type": "string", "maxLength": 50}, "requires_escalation": {"type": "boolean"} } } def classify_with_validation(email_body: str) -> dict: result = classify_email(email_body) validate(instance=result, schema=SCHEMA) # スキーマ不一致時はValidationErrorをraise return result
3. Ollama Python SDKを使う場合
公式のollama Pythonライブラリ(pip install ollama)でも同様にJSON modeを指定できます。
import ollama import json response = ollama.chat( model="llama3.3:70b-instruct-q4_0", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "請求書の金額が違います"} ], format="json" ) result = json.loads(response["message"]["content"]) print(result)
requests を使った実装と機能的には同等ですが、SDKを使うとバージョン互換性の管理をライブラリ側に委ねられるメリットがあります。どちらを選んでも動作は変わりません。
実務ユースケース3選
1. 社内メール・問い合わせの自動分類
問い合わせメールをカテゴリ・優先度・担当部門に自動振り分けするシステムへの組み込みが代表的なユースケースです。ローカルLLMなので、顧客情報を含むメール本文を外部サービスに送らずに処理できます。知人のインフラエンジニアが社内ヘルプデスクに組み込んだ事例では、1日200件の問い合わせ仕分けを自動化し、担当者が最初に内容を確認してルーティングする工数を大幅に削減できたと聞いています。
機密データを外部AIに送れない事情と、ローカルLLMが代替手段として有効な場面の整理については 社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢 で詳しく解説しています。
2. Linuxサーバーログの構造化抽出
/var/log/syslog や /var/log/nginx/access.log の非構造化テキストから、エラーコード・タイムスタンプ・送信元IPを構造化して抽出するパイプラインを作れます。通常はAWKやsedでパターンマッチングしますが、不規則なフォーマットのログやアプリケーション独自のログには正規表現が複雑になりがちです。JSON modeを使ったLLMベースの抽出は、フォーマットが揺れるログに対して柔軟に対応できます。
# ログ構造化用のシステムプロンプト例 あなたはサーバーログ解析AIです。 入力されたログ行から情報を抽出し、以下のJSONのみで返してください。 {"timestamp": "ISO8601形式", "level": "ERROR" | "WARN" | "INFO", "service": "サービス名", "message": "エラー内容の要約"} 見つからないフィールドはnullとすること。
3. 非構造化テキストからフォームデータを生成
「来週月曜の午後2時に渋谷で田中さんと打ち合わせ、議題はQ3予算」というような自然文から、日時・場所・参加者・議題をJSON形式で抽出し、カレンダーや社内DBへの自動登録フォームに流し込む用途にも活用できます。音声認識ツールと組み合わせると、口頭の指示から直接フォームデータを生成するパイプラインも構築できます。テキストのフォーマットが一定でない議事録や手書きメモのデジタル化にも応用しやすい構成です。
JSONDecodeErrorが発生するトラブルとリトライ設計
1. JSONDecodeErrorが発生する3つの原因
JSON modeを有効にしてもjson.loads() が失敗するケースが主に3つあります。・モデルが能力不足でJSONを正しく閉じられない — 7B以下の小さなモデルは複雑なスキーマに対応できないことがあります。ネストが深いJSONは途中で切れる場合があります
・max_tokensで出力が途中で打ち切られる — デフォルトの最大トークン数を超えると、JSONが閉じられないまま終了します。
num_predict: 2048 程度に緩めて再試行します・システムプロンプトが曖昧でモデルが迷う — スキーマの説明が不完全な場合、モデルが余計な説明フィールドを追加したり空のオブジェクトを返したりします
2. リトライロジックの実装
本番環境では、JSONDecodeErrorやバリデーションエラーが発生した場合に自動リトライする仕組みを入れます。import time from jsonschema import ValidationError def classify_with_retry(email_body: str, max_retries: int = 3) -> dict | None: for attempt in range(max_retries): try: result = classify_with_validation(email_body) return result except (json.JSONDecodeError, ValidationError) as e: print(f"attempt {attempt + 1}/{max_retries} failed: {e}") if attempt < max_retries - 1: time.sleep(1) return None # 全リトライ失敗時はNoneを返して呼び出し元で対処
3. モデル選定の指針
JSON modeの精度はモデルサイズと量子化レベルに強く依存します。現場での傾向をまとめます。・Llama3.3:70b-instruct-q4_0 — 複雑なスキーマにも安定して対応。本番向きの第一選択
・Mistral:7b-instruct-q8_0 — 軽量で動作が速い。スキーマがシンプルであれば実用的
・Phi-4:14b-q4_0 — GPUメモリが限られる環境でバランスが取れる選択肢
・Gemma 3:12b — 日本語テキストへの適合が良好。q8_0で精度が安定する
量子化レベルは
-q4_0 よりも -q8_0 の方がJSON modeでの精度が高い傾向があります。本番運用でエラー率が高い場合は量子化レベルを上げることも検討してください。【重要】本番環境でJSON modeを使う際は、必ずjsonschemaによるスキーマバリデーションとリトライロジックをセットで実装してください。JSON modeはJSONの文法的正しさのみを保証するため、想定外のフィールド構成が返ってくることがあります。バリデーションなしで業務DBに直接書き込む実装は避けることを強く推奨します。各モデルの性能比較の詳細は ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント を参照してください。
まとめ
OllamaのJSON modeを使うと、ローカルLLMの出力を安定した機械可読形式で取得できます。業務システムに組み込む際に必要な設定と実装のポイントを以下の表にまとめます。| やりたいこと | 設定・コマンド |
|---|---|
| JSON modeを有効にする | curl -d '{"format": "json", ...}' http://localhost:11434/api/chat |
| レスポンスを一括受け取りにする | curl -d '{"stream": false, ...}' http://localhost:11434/api/chat |
| Pythonでパースする(chat) | json.loads(response.json()["message"]["content"]) |
| Pythonでパースする(generate) | json.loads(response.json()["response"]) |
| スキーマをバリデーションする | jsonschema.validate(instance=result, schema=SCHEMA) |
| JSONが途中で切れる場合に上限を緩める | curl -d '{"num_predict": 2048, ...}' http://localhost:11434/api/chat |
| Few-shot例で精度を高める | システムプロンプトに【入力例】【出力例】のペアを1~2件追記 |
| 本番でエラー率が高い場合 | llama3.3:70b-instruct-q8_0 に切り替えるか量子化レベルを上げる |
ローカルLLMのJSON mode活用を2日間のハンズオンで体験する
この記事で解説したJSON modeの実装を、実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 前のページへ:OllamaのOpenAI互換APIを使う方法|既存OpenAIコードをそのままローカルLLMに切り替える手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます