- ローカルモデルの応答を JSON Schema に合わせて制限できるため、後処理のパース負荷を減らし、データ抽出結果をより一貫したものにできる
- ユーザーは cURL、Python、JavaScript で
format パラメータ にスキーマを渡し、同じ方法で構造化出力を要求できる
- Python は Pydantic、JavaScript は Zod を併用すると、スキーマ定義と応答検証をコードレベルで結び付けやすい
- 例では国情報、ペットのテキスト、
llama3.2-vision の画像分析結果を、定義済みの JSON フィールドとして返し検証する
- 安定した結果が必要な場合は、プロンプトに「return as JSON」を入れ、temperature を 0 に下げる設定が推奨される
JSON Schemaでモデル応答を制限
- Ollama は、モデル出力が特定の JSON Schema 形式に従うよう制限する構造化出力をサポートしている
- Ollama の Python および JavaScript ライブラリも、構造化出力をサポートするよう更新された
- この機能は次の作業に活用できる
- 文書からのデータ解析
- 画像からのデータ抽出
- 言語モデル応答の構造化
- JSON mode より高い信頼性と一貫性の確保
インストールと呼び出し方法
- 最新版の Ollama をダウンロードする必要がある
- Python ライブラリは次のコマンドで最新版に更新する
pip install -U ollama
- JavaScript ライブラリは次のコマンドでインストールする
npm i ollama
- 構造化出力を要求する際は、cURL リクエストや Python・JavaScript ライブラリの
format パラメータ にスキーマを渡す
cURLでJSON Schemaを渡す
- cURL の例では
http://localhost:11434/api/chat にリクエストを送り、model は llama3.1、stream は false に設定する
format には name、capital、languages を持つオブジェクトスキーマを入れる
name: 文字列
capital: 文字列
languages: 文字列配列
- 3 つのフィールドはすべて
required に指定される
- 応答は、リクエストに含まれた JSON Schema 形式で返される
{
"capital": "Ottawa",
"languages": [
"English",
"French"
],
"name": "Canada"
}
PythonとJavaScriptライブラリの使用
-
Python
- Ollama Python library は、スキーマを JSON オブジェクトとして
format パラメータに渡す
- スキーマは
dict として渡せ、推奨される方法は Pydantic の model_json_schema() でシリアライズすること
- 例では
Country モデルに name、capital、languages フィールドを定義し、応答を Country.model_validate_json() で検証する
country = Country.model_validate_json(response.message.content)
- 出力例では
Canada、Ottawa、English、French が定義されたフィールドに合わせて返される
-
JavaScript
- Ollama JavaScript library は、スキーマを JSON オブジェクトとして
format パラメータに渡す
- スキーマは
object として渡せ、推奨される方法は Zod と zodToJsonSchema() を使うこと
- 例では
Country スキーマを Zod オブジェクトとして定義し、応答本文を JSON.parse() した後に Country.parse() で検証する
const country = Country.parse(JSON.parse(response.message.content));
テキストと画像からのデータ抽出
-
ペットのテキスト抽出
- 構造化出力は、テキストから必要な情報を取り出す際に使える
- 例では
Pet と PetList の Pydantic モデルを定義し、ペット情報を JSON 構造で返す
Pet: name、animal、age、color、favorite_toy
PetList: pets 配列
- 入力テキストには 2 匹の猫の情報が含まれる
- Luna: 5歳、灰色の毛、yarn が好き
- Loki: 2歳、黒色、tennis balls が好き
- 出力は、定義されたスキーマに従って
Pet オブジェクトの一覧として検証される
-
ビジョンモデルの画像説明
- 構造化出力は ビジョンモデル と一緒に使用できる
- 例では
llama3.2-vision で画像を分析し、ImageDescription スキーマに合わせた結果を返す
- スキーマには次のフィールドが含まれる
summary
objects
scene
colors
time_of_day
setting
text_content
- 例のリクエストでは、画像内のオブジェクト、場面、色、検出可能なテキストを分析するよう指示する
- オプションで
temperature を 0 に設定し、より 決定的な出力 を使う
- 出力例では、ビーチのヤシの木の場面を要約し、
tree と beach のオブジェクト、色、時間帯、屋外設定を構造化フィールドで返す
OpenAI互換APIの使用
- OpenAI 互換の例では、
OpenAI クライアントを base_url="http://localhost:11434/v1" と api_key="ollama" で設定する
client.beta.chat.completions.parse() を使い、response_format に Pydantic モデルの PetList を渡す
- 応答では
completion.choices[0].message を確認し、parsed があればパース済み結果を出力する
refusal があれば拒否応答を出力し、openai.LengthFinishReasonError はトークンが多すぎる場合として処理する
安定した出力設定と今後の計画
- 応答スキーマの定義には、Python の Pydantic または JavaScript の Zod の使用が推奨される
- モデルがリクエストを理解できるよう、プロンプトに「return as JSON」を追加するのがよい
- より決定的な出力が欲しい場合は、temperature を 0 に設定する
- 今後の計画は次のとおり
- 制御された生成のための logits の公開
- 構造化出力の性能と精度の改善
- サンプリングのための GPU アクセラレーション
- JSON Schema を超える追加形式のサポート
1件のコメント
Hacker News のコメント
より強い出力制約が必要なら、llama.cpp は GBNF をサポートしている
https://github.com/ggerganov/llama.cpp/blob/master/grammars/...
単純に考えると、モデルは JSON をはるかに多く見ているので、他の形式より JSON をうまく生成できそうに思える
朗報だ
CSV データを作るとき、前後に「Here is your data」や「Please note blah blah」のような不要な文言を付けずに、普通のプロンプトをどう組み立てればよいか悩んでいたが、これで望む返却形式を正確に定義して、構造化出力をそのまま CSV に渡せるのでうれしい
そうしないと、技術的には CSV 形式だが意味のない出力になる可能性がある。モデルは本当は段落形式の回答を書こうとしていたのに、トークンサンプラーが、モデルがあまり言いたがっていない低確率のトークンを選んだ結果かもしれない
\n であらかじめ埋めておき、で停止させると、この種の問題を防げる動いた。gemma2:2b に以下の文を入れたところ、望みどおりの JSON が出てきた
You have spent 190 at Fresh Mart. Current balance: 5098結果は
{"amount": 190, "balance": 5098, "category": "Shopping", "place": "Fresh Mart"}だった本当に驚きだ。実際に欲しかった機能の一つだった
ollama が良いのは、LLM を他の UNIX プログラムのように使っている感覚を与えてくれるからで、LLM が UNIX に自然に属しているように感じさせてくれる
ただ、AMD GPU でうまく動かせた人がいるのか気になる。より難しいと聞いたが、来年カードを買うときには競合を応援したい
780M 搭載のミニ PC で、BIOS 経由で共有メモリを 16GB 割り当てたところ、かなりうまく動いた
こうした制約が LLM の出力品質にどんな影響を与えるのか気になる
場合によっては、出力品質が高いなら Markdown やプレーンテキストをパースする方を選びたい
1回目は重いモデルと自然言語を使い、Markdown セクションで推論を扱わせ、最終的な自然言語の回答を提供させる。可能なら Markdown ヘッダーで明確にラベル付けする
2回目は、より安価で高速なモデルでその回答を構造化出力形式に変換し、パイプラインの非 LLM 部分が消費できるようにする
基本的には、JSON スキーマモードで曖昧な自然言語部分の周囲にきれいな境界を作り、LLM に自分の出力を有用な形式で取り込むプリプロセッサの役割をさせることになる
さらに、モデルにスキーマを知らせる必要がある。知らせないと、奇妙なトークン化の問題がより多く発生する
例えば、スキーマが JSON キー
"foobarbaz"を期待していて、標準的な BPE トークン化が["foobar", "baz"]だとすると、現在の制約付き出力ライブラリが作るトークンマスクは、モデルが"f"、"foo"、"foobar"のいずれかを選ぶことを許可しうる。モデルが"foo"を選ぶと、制約は次のトークンとして例えば"bar"と"baz"を強制する。するとモデルは["foobar", "baz"]ではなく["foo", "bar", "baz"]を見ることになり、混乱する [0]プロンプトで
"foobarbaz"がスキーマキーの一つだとモデルが知っていれば、通常は"foo"より"foobar"を好む[0] 最新モデルでは正規化のため、こうしたトークン同士には関連はあるが、同じものではない
LLM とプロンプトがもともと JSON 応答を作る可能性があるかに大きく左右される。LLM を無理に誘導すればするほど、まともな入力を作る可能性は低くなる
小さなモデルでは、意味のある予測力がある空間の端により早く到達し、出力がランダムノイズに近づき始める
厳密な測定ではなく、複数の LLM プロジェクトに多くの時間を費やした後の感覚による判断だ。この特定のツールはまだ使っていないが、ollama も以前、似た手法に見える方法で JSON 出力を保証できたし、パートナーと一緒に、別の LLM ランタイムツールである oobabooga 向けに jsonformer に似たものを作業したこともある
そうした変更が入れば、JSON に限定しない一般的な構造生成も可能になることを期待している
トークンを大量に消費しがちだが、やろうとしていることがそのコストを正当化するなら、かなり強く押し進められる。絶対的な最高品質ではなくても、努力なしで 95% レベルまで得られるツールなら、ツールボックスに入れておく価値は十分にある
サポートされているどのモデルにも使えるのか気になる
自分のハードウェアでは 1B〜3B モデルしか安定して動かせないので聞いている
小さなモデルでは結果がばらつくことがありますが、「x を JSON で返せ」と依頼すると精度の助けになる傾向がありました
この機能に関する PR はほぼ 1年 開いたままだった
メンテナーたちがあまりにも静かだった点は少し残念だ
来年はコミュニティ PR をよりきちんと見て、マージできるようにしたいです
いまだに oobabooga を使っている。exlv2 対応のおかげで、デュアル 3090 でずっと効率的に推論できる
記憶では exl2 のほうが速いとはいえ、特に低いビット深度では gptq 量子化のほうが精度面で優れていたように思う
outlinesと比べたとき、どんな付加価値があるのか気になるhttps://www.souzatharsis.com/tamingLLMs/notebooks/structured...
最近の outlines/xgrammar 関連の研究を踏まえて、より多くの形式をサポートし、精度を高め、性能を改善できるようサンプリングを更新していければと考えています
LLM に 構造化された入力 を与える最善の方法があるのか気になる
たとえば 100 個の文を入れて、それぞれを複数の方法で分類させるような場合。構造化データを受け取るのは簡単だが、行番号を先頭に付ける自分の方法は無骨に感じる
ただしこの場合は、モデルが混乱しないよう文を 1 つずつ与えるのがいちばん良い
プロンプトを
"次の文を分類せよ。ルールは ..."+ 文、という形で構成すればプレフィックスキャッシュを活用できるので、一度に問い合わせるより性能も良くなる可能性があるもちろん、プレフィックスキャッシュがあり、入力トークンごとに課金されない場合に可能な方法。最近はほとんどのプロバイダーが、プレフィックスキャッシュを使う意思を示せば、より低コストで使えるようにしてくれる