概要
Guardrails AI1を用いてローカルLLMの出力を検査&Parseする方法をまとめた記事.
Structured Outputs(構造化出力)2とは
モデルが常に指定されたJSONスキーマに従った応答を生成することを保証する機能でありOpenAIのAPIで提供されている機能
本記事はStructured OutputsっぽいことをローカルLLMで実現する手法を紹介しています. 出力を100%保証するものではありません
Guardrails AIを用いることで期待しない出力の検出や出力のParseが簡単に行えます.
sample
Hikakkun/local-llm-structured-output(Github)
Guardrails AI1とは
ローカルのLLMから信頼性の高い出力を得るために設計されたPythonフレームワーク
- LLMの入力と出力に対してGuardを適用し、期待しない出力やリスクを検出
- LLMからJSONなどの構造化データを生成し, 信頼性の高いデータ処理を支援
- 以下のコードだけでLLMの出力を検査&Parseすることが可能
-
pip install guardrails-ai
でインストール可能
from pydantic import BaseModel from guardrails import Guard class Human(BaseModel): name : str age : int guard = Guard.for_pydantic(Human) validated_output = guard.parse("LLMからの出力") if validated_output.validation_passed: #validated_output.validated_outputはHumanクラスのコンストラクタに使用できる human = Human(**validated_output.validated_output) else: #エラー処理やLLMで再度処理を実行する etc.
-
使用したLLMとタスク
- 使用モデル
gemma-2-2b-jpn-it-IQ4_XS.gguf
3- 日本語版 Gemma 2 2B4をgguf化し4bit量子化を行ったもの
- 実行するタスクは以下で定義されるキャラクターの生成
from enum import Enum, auto from pydantic import BaseModel, Field class Weapon(Enum): def _generate_next_value_(name, start, count, last_values): # これを定義しておくと Staff = "Staff"をauto()で行ってくれる return name Staff = auto() Sword = auto() Bow = auto() FryingPan = auto() DualBlades = auto() Gun = auto() class Character(BaseModel): name: str = Field( description="キャラクターの名前", ) weapon: Weapon = Field( description="キャラクターが使用する武器の種類", ) description: str = Field( description="キャラクター得意技, 生い立ちなどの説明", )
- キャラクターを生成するためのprompt
- Code blocks内のJSON Schemaは
class Character
からCharacter.model_json_schema()
で取得可能 - 詳しくはGitHubの
main.py
を参考にしてください
<start_of_turn>user 出力は次のJSON Schemaに準拠し、Json形式で行うこと。 ```json { "$defs": { "Weapon": { "enum": ["Staff", "Sword", "Bow", "FryingPan", "DualBlades", "Gun"], "title": "Weapon", "type": "string" } }, "properties": { "name": { "description": "キャラクターの名前", "title": "Name", "type": "string" }, "weapon": { "$ref": "#/$defs/Weapon", "description": "キャラクターが使用する武器の種類" }, "description": { "description": "キャラクター得意技などのキャラクターを体現するような説明", "title": "Description", "type": "string" } }, "required": ["name", "weapon", "description"], "title": "Character", "type": "object" } ``` キャラクターを生成して<end_of_turn> <start_of_turn>model <end_of_turn>
- Code blocks内のJSON Schemaは
上記のpromptをLLMで処理する上の問題点
- JSON形式で出力される保証はない
- 700回ほど生成して4回エラー
- Gemma 自体が結構性能いい
-
max_tokens
の数を減らせば解決される? - この程度であれば正規表現でなんとかなりそうだが複雑なJSONを出力する場合は少し面倒
面倒な出力例
- 解説が書かれている
```json { "name": "Aoi Sakura", "weapon": "Bow", "description": "優雅で落ち着いた性格だが、... (省略) ...一発で敵を倒すことができる。" } ``` **解説** * **name:** キャラクターの名前 * **weapon:** キャラクターが使用する武器の種類(Bow) * **description:** キャラクターの性格や特徴、得意技などを記述
- JSONとしてエラー
- descriptionのvalueの締めの
"
がない
{ "name": "アリス", "weapon": "Staff", "description": "静かな力を...(省略) ...周囲を揺さぶる。」 }
- descriptionのvalueの締めの
- 700回ほど生成して4回エラー
Guardrails AI1を用いた解決法
- 出力が
Character
に則しているかの判定とParseが可能from guardrails import Guard guard = Guard.for_pydantic(Character) validated_output = guard.parse("LLMからの出力") if validated_output.validation_passed: #これ以下は出力がCharacterであることが保証されている character_json = validated_output.validated_output character = Character(**character_json) else: # エラーだったvalidated_output.errorにエラー内容が格納されている error = validated_output.error
-
出力例 1
もguard.parse
を用いれば問題なくJSON部分を抽出してくれる -
出力例 2
はvalidated_output.error="Unterminated string starting at: line 4 column 22 (char 69)"
-
-
Guard.for_pydantic
の他にもHTMLのような定義の仕方も可能-
Generate structured data
from guardrails import Guard guard = Guard.for_rail_string(""" <rail version="0.1"> <output> <string name="name" /> <integer name="age" /> <boolean name="is_employed" /> </output> </rail> """)
-
Generate structured data