ローカルでLLMを動かす Ollama は、手軽さの一方で「戻り値が安定しない」という悩みがつきまといます。
特に JSON を返してほしい場面で、コードフェンスや前置きが混ざってパースに失敗した経験はないでしょうか。
この問題は、プロンプトの工夫ではなく Ollama の「構造化出力(structured outputs)」で根本から解決できます。
format パラメータと Pydantic を組み合わせれば、型安全な JSON を確実に受け取れます。
なぜプロンプトで JSON を頼むと崩れるのか
「JSON で返して」と指示する方法(JSONモード)は、出力が整形済みである保証がありません。
Ollama の公式ブログは、構造化出力を「JSON モードより信頼性と一貫性が高い」と説明しています。
残りはコードフェンスや説明文が混ざり、json.loads でそのまま落ちてしまうわけです。
プロンプトを工夫してもこの不安定さは消えにくく、後処理のパース用コードが膨らみがちでした。
format パラメータにスキーマを渡す
Ollama 0.5(2024年12月リリース)から、format パラメータに JSON スキーマそのものを渡せるようになりました。
出典: Structured outputs(Ollama 公式ブログ)
スキーマを渡すと constrained decoding(制約付きデコード)が働き、トークン生成の段階でスキーマに合わない出力が弾かれます。
つまりモデルは「スキーマに従った文字列しか吐けない」状態になるのです。
結果として、整形済みの JSON が 100% 返るようになります。
最小の例を見てみましょう。
from ollama import chat
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
},
"required": ["name", "age"],
}
res = chat(
model="llama3.2",
messages=[{"role": "user", "content": "山田太郎は30歳です"}],
format=schema,
)
print(res.message.content)
format に渡すのは、普通の dict で表現した JSON スキーマです。
戻り値の content は、もう余計な装飾のない JSON 文字列になっています。
Pydantic でスキーマを生成・検証する
スキーマを手書きするのは骨が折れるので、Pydantic と組み合わせるのが実用的です。
BaseModel を定義し、model_json_schema() でスキーマを生成して format に渡します。
戻ってきた文字列を model_validate_json() に通せば、生成と検証が一気通貫になります。
from ollama import chat
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
res = chat(
model="llama3.2",
messages=[{"role": "user", "content": "山田太郎は30歳です"}],
format=Person.model_json_schema(),
)
person = Person.model_validate_json(res.message.content)
print(person.name, person.age)
これで person は型付きのオブジェクトとして扱え、フィールド名の打ち間違いもエディタが教えてくれます。
スキーマの定義と検証が一箇所にまとまり、保守もずいぶん楽になりました。
速度面の副次効果
構造化出力は信頼性だけでなく、速度にも効くことがあります。
スキーマに沿わない出力が生成の段階で弾かれるので、整形のやり直しに費やしていたトークンがまるごと不要になるのです。
どれくらい速くなるかはモデルやハードウェアに左右されるため、数値は手元で測ってから判断してください。
最後に
ローカルLLM から構造化データが欲しいときは、プロンプトで JSON をお願いするより、推論そのものをスキーマで縛る方が確実です。
これは Ollama に限らず、クラウドAI の structured outputs にも共通する発想だと感じています。
Ollama 自体の導入がまだの方は、別記事の入門編も参考にしてみてください。
以上です。










コメントを残す