こんにちは、かつコーチです。
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に実際の処理を任せる方法を学びましょう
コメント