【Laravel】構造化出力(Structured Output)でJSON形式の応答を得る

laravelアイキャッチ Laravel

こんにちは、かつコーチです。

AIチャットは文章で返してくれれば十分ですが、業務システムに組み込むとなると話は別です。

「感情分析の結果をDBに保存したい」「フォームの項目を自動入力したい」となると、決まった形式のデータが必要になります。

この記事では、構造化出力(Structured Output)を使ってLLMの応答をJSON形式に固定する方法を解説します。

実際に実装した際、LLMが期待通りのスキーマで返してくれずハマった一次情報もあわせて紹介します。

構造化出力とは?

自由な文章からJSONへ

構造化出力とは、LLMにJSON Schema(JSONの形式・型・必須項目を定義した仕様書のようなもの)を渡し、その形式通りのJSONで応答させる機能です。

通常、LLMは自由な文章で返しますが、JSON Schemaを指定することで「この項目は文字列」「この項目は1〜5の数値」といった制約を守らせられます。

これにより、AIの応答をそのままDBの保存処理やAPIレスポンスに使えるようになります。

どんな場面で使うのか

代表的なのはレビュー文からの感情分析です。

「このレビューはポジティブかネガティブか、スコアは何点か」をJSONで受け取り、そのままDBに保存する、といった使い方です。

他にも、問い合わせ内容からカテゴリとタグを自動抽出する、フォームの入力候補を生成する、といった用途に向いています。

基本の書き方 / 実装手順

JSON Schemaの定義方法

レビュー文の感情分析を例に、JSON Schemaを定義してみます。

// app/Schemas/ReviewSentimentSchema.php
namespace App\Schemas;

use Prism\Prism\Schema\ObjectSchema;
use Prism\Prism\Schema\StringSchema;
use Prism\Prism\Schema\NumberSchema;
use Prism\Prism\Schema\EnumSchema;

class ReviewSentimentSchema
{
    public static function make(): ObjectSchema
    {
        return new ObjectSchema(
            name: 'review_sentiment',
            description: 'レビュー文の感情分析結果',
            properties: [
                new EnumSchema(
                    name: 'sentiment',
                    description: '感情の分類',
                    options: ['positive', 'neutral', 'negative'],
                ),
                new NumberSchema(
                    name: 'score',
                    description: 'ポジティブ度合いを1〜5で表したスコア',
                ),
                new StringSchema(
                    name: 'summary',
                    description: 'レビュー内容の要約(30文字以内)',
                ),
            ],
            requiredFields: ['sentiment', 'score', 'summary'],
        );
    }
}

実際の活用例:感情分析結果をJSONで取得

このスキーマを使って、実際にLLMへリクエストを送ります。

// app/Http/Controllers/ReviewAnalysisController.php
namespace App\Http\Controllers;

use App\Schemas\ReviewSentimentSchema;
use Illuminate\Http\Request;
use Prism\Prism\Prism;
use Prism\Prism\Enums\Provider;

class ReviewAnalysisController extends Controller
{
    public function analyze(Request $request)
    {
        $reviewText = $request->input('review_text');

        $response = Prism::structured()
            ->using(Provider::OpenAI, 'gpt-4o-mini')
            ->withSchema(ReviewSentimentSchema::make())
            ->withPrompt("以下のレビューを分析してください。\n\n{$reviewText}")
            ->asStructured();

        $result = $response->structured;
        // $result = ['sentiment' => 'positive', 'score' => 4, 'summary' => '...']

        return response()->json($result);
    }
}

asStructured() を呼ぶと、スキーマに沿ったPHPの連想配列がそのまま返ってくるので、パース処理を自前で書く必要がありません。

よくあるつまずきポイント・エラー対処

スキーマ通りに返ってこなかった話

実装した直後、テストで数十件のレビュー文を投げてみたところ、まれに score が数値ではなく "4点" という文字列で返ってくることがありました。

DBの score カラムを integer 型にしていたため、保存しようとしたタイミングで次のエラーが出ました。

SQLSTATE[HY000]: General error: 1366 Incorrect integer value: '4点' for column 'score'

原因を調べると、プロンプトの中で「スコアを教えてください」という自然文の指示も残していたため、LLMがJSON Schemaの制約より自然文の指示を優先してしまうケースがあったのです。

構造化出力を使うときは、プロンプト側の文言もスキーマの定義と矛盾しないように統一する必要があると学びました。

❌Before → ✅After

❌Before:プロンプトに「〇点で教えてください」と自然文で書いてしまい、LLMが文字列を混ぜて返すことがあった。

✅After:プロンプトからは数値に関する自然文の表現を削除し、スキーマのdescriptionだけで指示を完結させた。

// 修正前のプロンプト(曖昧な指示が混在)
$prompt = "レビューを分析して、スコアを『5点満点で3点』のように教えてください。";

// 修正後のプロンプト(形式の指示はスキーマに任せる)
$prompt = "以下のレビューの感情を分析してください。\n\n{$reviewText}";

さらに、念のためアプリ側でもバリデーションをかけ、想定外の型が来た場合は再リクエストするフォールバックを入れておくと安心です。

if (! is_numeric($result['score'])) {
    // 型がおかしい場合は再リクエストするなどのフォールバック処理
    throw new \RuntimeException('構造化出力のscoreが数値ではありません: ' . json_encode($result));
}

応用・一歩先の使い方

ネストしたスキーマ

ObjectSchema は入れ子にできるので、「レビューの中の複数の指摘ポイントを配列で受け取る」といった複雑な構造も表現できます。

フォーム自動入力への応用

問い合わせフォームの本文を渡し、氏名・カテゴリ・優先度などをJSON Schemaで定義して抽出すれば、入力補助機能を簡単に作れます。

次回はさらに一歩進んで、LLMにアプリの関数そのものを呼ばせる「ツール呼び出し」を扱います。

まとめ

この記事のポイント

  • 構造化出力はJSON Schemaを渡すことで、LLMの応答形式を固定できる仕組み
  • レビューの感情分析やフォーム自動入力など、業務システムへの組み込みに向いている
  • プロンプトの自然文とスキーマの指示が矛盾すると、型崩れが起きることがあるため注意する

次に読むべき記事

  • 前の記事「会話履歴を管理するチャットUIを実装する」も合わせてどうぞ
  • 次は「ツール呼び出し(Function Calling)でAIにアプリの操作を行わせる」で、AIに実際の処理を任せる方法を学びましょう

コメント

タイトルとURLをコピーしました