5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカルLLM(Gemma 2 2B)で「Structured Outputs(構造化出力)」っぽいことGuardrails AIで実現する

Posted at

概要

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フレームワーク

  1. LLMの入力と出力に対してGuardを適用し、期待しない出力やリスクを検出
  2. 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.gguf3
    • 日本語版 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()で取得可能
    • 詳しくはGitHubmain.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>
    

上記のpromptをLLMで処理する上の問題点

  • JSON形式で出力される保証はない
    • 700回ほど生成して4回エラー
      • Gemma 自体が結構性能いい
      • max_tokensの数を減らせば解決される?
      • この程度であれば正規表現でなんとかなりそうだが複雑なJSONを出力する場合は少し面倒

    面倒な出力例

    1. 解説が書かれている
      ```json
      {
        "name": "Aoi Sakura",
        "weapon": "Bow",
        "description": "優雅で落ち着いた性格だが、... (省略) ...一発で敵を倒すことができる。" 
      }
      ``` 
      
      
      
      **解説**
      
      * **name:** キャラクターの名前
      * **weapon:** キャラクターが使用する武器の種類(Bow)
      * **description:** キャラクターの性格や特徴、得意技などを記述
      
    2. JSONとしてエラー
      • descriptionのvalueの締めの"がない
      {
        "name": "アリス",
        "weapon": "Staff",
        "description": "静かな力を...(省略) ...周囲を揺さぶる。」
      }
      

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
    
    • 出力例 1guard.parseを用いれば問題なくJSON部分を抽出してくれる
    • 出力例 2validated_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>
      """)    
      
  1. Guardrails AI 2 3

  2. Structured Outputs

  3. alfredplpl/gemma-2-2b-jpn-it-gguf

  4. 日本語版 Gemma 2 2B を公開

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?