2 ポイント 投稿者 GN⁺ 2024-12-09 | 1件のコメント | WhatsAppで共有
  • ローカルモデルの応答を 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 にリクエストを送り、modelllama3.1streamfalse に設定する
  • format には namecapitallanguages を持つオブジェクトスキーマを入れる
    • name: 文字列
    • capital: 文字列
    • languages: 文字列配列
    • 3 つのフィールドはすべて required に指定される
  • 応答は、リクエストに含まれた JSON Schema 形式で返される
{
  "capital": "Ottawa",
  "languages": [
    "English",
    "French"
  ],
  "name": "Canada"
}

PythonとJavaScriptライブラリの使用

  • Python

    • Ollama Python library は、スキーマを JSON オブジェクトとして format パラメータに渡す
    • スキーマは dict として渡せ、推奨される方法は Pydanticmodel_json_schema() でシリアライズすること
    • 例では Country モデルに namecapitallanguages フィールドを定義し、応答を Country.model_validate_json() で検証する
country = Country.model_validate_json(response.message.content)
  • 出力例では CanadaOttawaEnglishFrench が定義されたフィールドに合わせて返される
  • JavaScript

    • Ollama JavaScript library は、スキーマを JSON オブジェクトとして format パラメータに渡す
    • スキーマは object として渡せ、推奨される方法は ZodzodToJsonSchema() を使うこと
    • 例では Country スキーマを Zod オブジェクトとして定義し、応答本文を JSON.parse() した後に Country.parse() で検証する
const country = Country.parse(JSON.parse(response.message.content));

テキストと画像からのデータ抽出

  • ペットのテキスト抽出

    • 構造化出力は、テキストから必要な情報を取り出す際に使える
    • 例では PetPetList の Pydantic モデルを定義し、ペット情報を JSON 構造で返す
      • Pet: nameanimalagecolorfavorite_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 に設定し、より 決定的な出力 を使う
    • 出力例では、ビーチのヤシの木の場面を要約し、treebeach のオブジェクト、色、時間帯、屋外設定を構造化フィールドで返す

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件のコメント

 
GN⁺ 2024-12-09
Hacker News のコメント
  • より強い出力制約が必要なら、llama.cppGBNF をサポートしている
    https://github.com/ggerganov/llama.cpp/blob/master/grammars/...

    • まさにそれを使っているように見える
    • 任意の文法に対する出力品質に満足できたのか気になる
      単純に考えると、モデルは JSON をはるかに多く見ているので、他の形式より JSON をうまく生成できそうに思える
    • どの点でより強力なのか気になる
  • 朗報だ
    CSV データを作るとき、前後に「Here is your data」や「Please note blah blah」のような不要な文言を付けずに、普通のプロンプトをどう組み立てればよいか悩んでいたが、これで望む返却形式を正確に定義して、構造化出力をそのまま CSV に渡せるのでうれしい

    • それでも、プロンプトが正しい文脈に入るように、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"} だった

    • かなり一貫性のない JSON だが、非常に小さなモデルで、しかも gemma ならそういうものだろう
  • 本当に驚きだ。実際に欲しかった機能の一つだった
    ollama が良いのは、LLM を他の UNIX プログラムのように使っている感覚を与えてくれるからで、LLM が UNIX に自然に属しているように感じさせてくれる
    ただ、AMD GPU でうまく動かせた人がいるのか気になる。より難しいと聞いたが、来年カードを買うときには競合を応援したい

    • 可能だ。内蔵 GPU でも動く
      780M 搭載のミニ PC で、BIOS 経由で共有メモリを 16GB 割り当てたところ、かなりうまく動いた
  • こうした制約が LLM の出力品質にどんな影響を与えるのか気になる
    場合によっては、出力品質が高いなら Markdown やプレーンテキストをパースする方を選びたい

    • OpenAI のモデルを使うときは、追加のトークンコストを負担できるなら、2回通す戦略が非常に良かった
      1回目は重いモデルと自然言語を使い、Markdown セクションで推論を扱わせ、最終的な自然言語の回答を提供させる。可能なら Markdown ヘッダーで明確にラベル付けする
      2回目は、より安価で高速なモデルでその回答を構造化出力形式に変換し、パイプラインの非 LLM 部分が消費できるようにする
      基本的には、JSON スキーマモードで曖昧な自然言語部分の周囲にきれいな境界を作り、LLM に自分の出力を有用な形式で取り込むプリプロセッサの役割をさせることになる
    • モデルが JSON 出力にどれだけファインチューニングされているかによる
      さらに、モデルにスキーマを知らせる必要がある。知らせないと、奇妙なトークン化の問題がより多く発生する
      例えば、スキーマが 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 に限定しない一般的な構造生成も可能になることを期待している
    • instructor のようなツールの有用性について、自分は完全に間違っていたと言える
      トークンを大量に消費しがちだが、やろうとしていることがそのコストを正当化するなら、かなり強く押し進められる。絶対的な最高品質ではなくても、努力なしで 95% レベルまで得られるツールなら、ツールボックスに入れておく価値は十分にある
  • サポートされているどのモデルにも使えるのか気になる
    自分のハードウェアでは 1B〜3B モデルしか安定して動かせないので聞いている

    • ブログ記事の著者です。どのモデルでも使えるはずです
      小さなモデルでは結果がばらつくことがありますが、「x を JSON で返せ」と依頼すると精度の助けになる傾向がありました
  • この機能に関する PR はほぼ 1年 開いたままだった
    メンテナーたちがあまりにも静かだった点は少し残念だ

    • 記事の著者で、メンテナーの一人でもあります。同意します。メンテナー側の対応は遅く、全体としてもっと多くの貢献を促したいと思っています
      来年はコミュニティ PR をよりきちんと見て、マージできるようにしたいです
    • 雰囲気を見ると、会社中心の道に進んでいるようで、そのためすべてをそのレンズと収益最大化の観点から見ているように思える
    • 自分もこれを一緒に作るのに協力したかったが、会話が突然途切れてしまった
  • いまだに oobabooga を使っている。exlv2 対応のおかげで、デュアル 3090 でずっと効率的に推論できる

    • ooba はしばらく触っていないけれど、exl2 や q3k_s のような非均質な量子化方式の状況がどうなっているのか気になる
      記憶では exl2 のほうが速いとはいえ、特に低いビット深度では gptq 量子化のほうが精度面で優れていたように思う
  • outlines と比べたとき、どんな付加価値があるのか気になる
    https://www.souzatharsis.com/tamingLLMs/notebooks/structured...

    • ブログの筆者です。現在の実装は llama.cpp GBNF を使って素早く実装できました。現時点での最大の付加価値は、この機能をリリースしたという点です
      最近の outlines/xgrammar 関連の研究を踏まえて、より多くの形式をサポートし、精度を高め、性能を改善できるようサンプリングを更新していければと考えています
  • LLM に 構造化された入力 を与える最善の方法があるのか気になる
    たとえば 100 個の文を入れて、それぞれを複数の方法で分類させるような場合。構造化データを受け取るのは簡単だが、行番号を先頭に付ける自分の方法は無骨に感じる

    • モデルは Markdown、JSON、さまざまなプログラミング言語で学習されているので、そのどれかなら動くはず
      ただしこの場合は、モデルが混乱しないよう文を 1 つずつ与えるのがいちばん良い
      プロンプトを "次の文を分類せよ。ルールは ..." + 文、という形で構成すればプレフィックスキャッシュを活用できるので、一度に問い合わせるより性能も良くなる可能性がある
      もちろん、プレフィックスキャッシュがあり、入力トークンごとに課金されない場合に可能な方法。最近はほとんどのプロバイダーが、プレフィックスキャッシュを使う意思を示せば、より低コストで使えるようにしてくれる