コンテンツにスキップ

構造化出力の使い方

構造化出力の使い方

このページでは、PublicLLMコンポーネントでStructured Output(構造化出力)を使用する方法を解説します。Structured Outputを使うと、LLMの出力をJSON形式で取得でき、プログラムで処理しやすくなります。

Structured Outputとは?

Structured Outputは、LLMの出力を事前に定義したJSON Schemaに従った構造化データとして取得する機能です。

通常のLLM出力:

りんご、バナナ、オレンジ、ぶどうの4つが良いでしょう。

Structured Output:

{
  "choices": ["りんご", "バナナ", "オレンジ", "ぶどう"]
}

対応プロバイダー

Structured Outputは以下のプロバイダーで利用できます:

プロバイダー 対応モデル
OpenAI gpt-4o, gpt-4o-mini など
Anthropic claude-3-5-sonnet-20241022 など
Gemini gemini-1.5-pro, gemini-1.5-flash など

JSON Schema の書き方

基本構造

Structured Outputで使用するJSON Schemaは、以下の基本構造を持ちます:

{
  "type": "object",
  "properties": {
    "プロパティ名": {
      "type": "データ型",
      "description": "プロパティの説明"
    }
  },
  "required": ["必須プロパティ名"],
  "additionalProperties": false
}

重要なルール

OpenAI Structured Outputの制約(必須)

OpenAIのStrict modeを使用する場合、以下の制約があります:

  1. additionalProperties: false - オブジェクトには定義されたプロパティのみを含める
  2. すべてのプロパティをrequiredに含める - オプショナルなプロパティは避ける
  3. すべてのプロパティにdescriptionを含める - 各プロパティの説明は必須
  4. ネストしたオブジェクトにも同様のルールを適用

データ型一覧

基本的なデータ型

説明
string 文字列 "hello"
integer 整数 42
number 数値(小数含む) 3.14
boolean 真偽値 true / false
null null値 null
array 配列 [1, 2, 3]
object オブジェクト {"key": "value"}

文字列型の詳細

{
  "name": {
    "type": "string",
    "description": "ユーザーの名前"
  }
}

列挙型(enum)を使う場合:

{
  "emotion": {
    "type": "string",
    "enum": ["happy", "sad", "neutral", "angry"],
    "description": "感情の状態"
  }
}

数値型の詳細

整数:

{
  "score": {
    "type": "integer",
    "description": "スコア(1〜100)"
  }
}

数値(制約付き):

{
  "rating": {
    "type": "number",
    "minimum": 1,
    "maximum": 5,
    "description": "評価(1〜5)"
  }
}

配列型の詳細

文字列の配列:

{
  "tags": {
    "type": "array",
    "items": {
      "type": "string"
    },
    "description": "タグのリスト"
  }
}

配列の長さを指定:

{
  "choices": {
    "type": "array",
    "items": {
      "type": "string"
    },
    "minItems": 4,
    "maxItems": 4,
    "description": "4つの選択肢"
  }
}

オブジェクトの配列:

{
  "items": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "商品名"
        },
        "price": {
          "type": "integer",
          "description": "価格(円)"
        }
      },
      "required": ["name", "price"],
      "additionalProperties": false
    },
    "description": "商品リスト"
  }
}

ネストしたオブジェクト

{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "description": "ユーザー情報",
      "properties": {
        "name": {
          "type": "string",
          "description": "ユーザー名"
        },
        "age": {
          "type": "integer",
          "description": "年齢"
        }
      },
      "required": ["name", "age"],
      "additionalProperties": false
    }
  },
  "required": ["user"],
  "additionalProperties": false
}

実践的なスキーマ例

例1: 盛り上がり判定

会話の盛り上がり度を1〜5で判定する場合:

{
  "type": "object",
  "properties": {
    "excitement": {
      "type": "integer",
      "description": "会話の盛り上がり度(1〜5)"
    },
    "reason": {
      "type": "string",
      "description": "判定理由"
    }
  },
  "required": ["excitement", "reason"],
  "additionalProperties": false
}

例2: 感情分析

ユーザーの発言から感情を分析する場合:

{
  "type": "object",
  "properties": {
    "emotion": {
      "type": "string",
      "enum": ["happy", "sad", "angry", "surprised", "neutral"],
      "description": "検出された感情"
    },
    "confidence": {
      "type": "number",
      "description": "信頼度(0〜1)"
    }
  },
  "required": ["emotion", "confidence"],
  "additionalProperties": false
}

例3: 4択問題の生成

クイズの4択選択肢を生成する場合:

{
  "type": "object",
  "properties": {
    "question": {
      "type": "string",
      "description": "問題文"
    },
    "choices": {
      "type": "array",
      "items": {"type": "string"},
      "minItems": 4,
      "maxItems": 4,
      "description": "4つの選択肢"
    },
    "correct_index": {
      "type": "integer",
      "description": "正解のインデックス(0〜3)"
    }
  },
  "required": ["question", "choices", "correct_index"],
  "additionalProperties": false
}

例4: 会話フェーズの判定

会話のフェーズを判定する場合:

{
  "type": "object",
  "properties": {
    "phase": {
      "type": "string",
      "enum": ["greeting", "main_topic", "closing", "off_topic"],
      "description": "現在の会話フェーズ"
    },
    "should_transition": {
      "type": "boolean",
      "description": "次のフェーズに移行すべきか"
    },
    "next_phase": {
      "type": "string",
      "enum": ["greeting", "main_topic", "closing", "off_topic"],
      "description": "推奨される次のフェーズ"
    }
  },
  "required": ["phase", "should_transition", "next_phase"],
  "additionalProperties": false
}

例5: 複数の評価項目

複数の観点から評価を行う場合:

{
  "type": "object",
  "properties": {
    "evaluations": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "description": "評価カテゴリ"
          },
          "score": {
            "type": "integer",
            "description": "スコア(1〜10)"
          },
          "comment": {
            "type": "string",
            "description": "コメント"
          }
        },
        "required": ["category", "score", "comment"],
        "additionalProperties": false
      },
      "description": "評価リスト"
    },
    "overall_score": {
      "type": "integer",
      "description": "総合スコア"
    }
  },
  "required": ["evaluations", "overall_score"],
  "additionalProperties": false
}

PublicLLMでの使用方法

基本的な設定

PublicLLMコンポーネントでresponse_schemaを設定すると、Structured Outputモードになります。
Structured Outputの結果はvalues.responseにdict形式で格納されます。

PublicLLMの設定項目

パラメータ
provider openai(またはanthropic, gemini
model gpt-4o-mini など
prompt Jinja2テンプレート
response_schema JSON Schema(JSON形式)

streamingは自動的にオフ

response_schemaを設定すると、streamingは自動的にfalseになります。Structured Outputはストリーミングをサポートしていません。

プロンプトの書き方

Structured Outputを使用する場合でも、プロンプトで期待する出力形式を説明することを推奨します:

会話の盛り上がり度を1〜5で判定してください。

【評価基準】
- 1: 会話が成立していない
- 2: 最低限の受け答え
- 3: 普通の会話
- 4: 活発な会話
- 5: 非常に盛り上がっている

【会話履歴】
{% for msg in values.history %}
{{ msg.role }}: {{ msg.text }}
{% endfor %}

JSON形式で回答してください。

実装パターン

パターン1: 盛り上がり判定 → State保存

graph LR
    A[DialogHistory] --> B[PublicLLM<br/>盛り上がり判定]
    B --> C[StateSet<br/>結果を保存]

    style A fill:#e8f5e9
    style B fill:#fff4e1
    style C fill:#e1f5ff

1. DialogHistory設定:

  • role_filter: ["user*", "assistant"]
  • max_history_items: 10

2. PublicLLM設定:

  • provider: openai
  • model: gpt-4o-mini
  • prompt:
    会話の盛り上がり度を1〜5で判定してください。
    
    {% for msg in values.history %}
    {{ msg.role }}: {{ msg.text }}
    {% endfor %}
    
  • response_schema:
    {
      "type": "object",
      "properties": {
        "excitement": {
          "type": "integer",
          "description": "盛り上がり度(1〜5)"
        }
      },
      "required": ["excitement"],
      "additionalProperties": false
    }
    

3. StateSet設定:

  • conversation.excitement: values.response.excitement

パターン2: Gateで条件付き実行

Structured Outputの結果をGateの条件として使用し、特定の条件を満たした場合のみ後続処理を実行する場合:

graph LR
    A[PublicLLM<br/>判定] --> B[StateSet<br/>結果を保存]
    B --> C[Gate<br/>ポジティブ判定]
    C --> D[特別な処理]

    style A fill:#fff4e1
    style B fill:#e1f5ff
    style C fill:#ffe8e8
    style D fill:#e8f5e9

PublicLLM設定:

  • response_schema:
    {
      "type": "object",
      "properties": {
        "is_positive": {
          "type": "boolean",
          "description": "ポジティブな内容かどうか"
        }
      },
      "required": ["is_positive"],
      "additionalProperties": false
    }
    

StateSet設定:

  • user.is_positive: values.response.is_positive

Gateの設定:

  • trigger: lambda: states.user.is_positive == True

Gateの動作

Gateは条件がTrueの場合のみ後続のコンポーネントを実行します。条件がFalseの場合、Gate以降の処理はスキップされます。


パターン3: 複数の値を一度に取得

複数の情報を一度のLLM呼び出しで取得する場合:

graph LR
    A[入力] --> B[PublicLLM<br/>複合分析]
    B --> C[StateSet<br/>複数値を保存]

    style B fill:#fff4e1
    style C fill:#e1f5ff

PublicLLM設定:

  • response_schema:
    {
      "type": "object",
      "properties": {
        "emotion": {
          "type": "string",
          "enum": ["happy", "sad", "neutral"],
          "description": "感情"
        },
        "topic": {
          "type": "string",
          "description": "話題"
        },
        "intent": {
          "type": "string",
          "enum": ["question", "statement", "request"],
          "description": "意図"
        }
      },
      "required": ["emotion", "topic", "intent"],
      "additionalProperties": false
    }
    

StateSet設定:

キー
user.emotion values.response.emotion
conversation.topic values.response.topic
user.intent values.response.intent

トラブルシューティング

よくある問題と解決策

1. スキーマエラーが発生する

原因: スキーマの構文が正しくない、または必須項目が不足している

解決策:
- JSONの構文を確認する(カンマの有無、括弧の対応など)
- additionalProperties: falseを忘れていないか確認
- すべてのプロパティがrequiredに含まれているか確認
- すべてのプロパティにdescriptionが含まれているか確認 (必須)

2. 期待した型が返ってこない

原因: プロンプトまたはdescriptionの説明が不十分

解決策:
- プロンプトで期待する出力形式を明確に説明する
- descriptionフィールドで各プロパティの意味と期待する値を詳しく説明する

3. nullが返ってくる

原因: LLMが適切な値を生成できなかった

解決策:
- プロンプトをより具体的にする
- enumを使う場合は、選択肢を適切に設定する
- temperatureを下げてより確定的な出力を得る

4. 配列の長さが期待通りでない

原因: minItems/maxItemsの制約が効いていない

解決策:
- プロンプトで明示的に「4つの選択肢を生成してください」などと指定する
- モデルによっては配列長の制約が完全に守られない場合がある


ベストプラクティス

1. シンプルなスキーマを心がける

複雑なネストは避け、できるだけフラットな構造を使用します。

{
  "type": "object",
  "properties": {
    "score": {
      "type": "integer",
      "description": "スコア(1〜10)"
    },
    "reason": {
      "type": "string",
      "description": "判定理由"
    }
  },
  "required": ["score", "reason"],
  "additionalProperties": false
}

2. descriptionは必須

OpenAIのStrict modeでは、すべてのプロパティにdescriptionを含める必要があります。descriptionはLLMがプロパティの意味を理解し、適切な値を生成するために重要です。

{
  "score": {
    "type": "integer",
    "description": "ユーザーの満足度スコア。1(非常に不満)〜5(非常に満足)で評価"
  }
}

descriptionがないとエラーになる

descriptionを省略すると、OpenAIのStrict modeではスキーマエラーが発生する可能性があります。

3. enumで選択肢を制限する

自由テキストよりもenumで選択肢を制限すると、より一貫した結果が得られます。

{
  "category": {
    "type": "string",
    "enum": ["greeting", "question", "complaint", "other"],
    "description": "発言のカテゴリ"
  }
}

4. プロンプトとスキーマを一致させる

プロンプトで説明する出力形式とスキーマが一致していることを確認します。

5. エラーハンドリングを考慮する

StateSetやGateでStructured Outputの結果を使用する際は、値が存在しない場合のフォールバックを考慮します。

# Gateのtrigger例(安全なアクセス)
lambda: values.response.get('is_positive', False) == True