生成AIは、チャットボットや文章生成ツールにとどまらず、業務システムの中核に組み込まれるケースが急速に増えています。しかし、生成AIを「システムの部品」として利用しようとした瞬間に直面するのが、出力の不安定さです。
自由文として読む分には自然で問題のない出力も、APIレスポンスやデータベース保存を前提とすると、一気に扱いづらくなります。
本記事では、
について整理します。
構造化出力とは、LLMの出力を、あらかじめ定義した構造に従わせる仕組みです。
従来は、
といった方法が一般的でした。
しかしこれらはあくまで“お願い”に過ぎません。モデルの性質上、フォーマットが崩れることは避けられません。
構造化出力では、スキーマを明示的に指定することで、モデル出力を構造化データとして取得できるようになります。
生成AIはしばしば“賢いAPI”のように扱われますが、本質は確率的な文章生成モデルです。
過去の記事でも触れた通り、LLMは意味を理解しているというよりも、文脈に対して“それらしい単語列”を確率的に生成しています。
そのため、
といった挙動は、バグではなくモデルの性質です。
しかし、システム連携では事情が異なります。
これらはすべて構造化データが前提です。
LLMの不確実性をそのまま流し込むと、
といった問題が発生します。
構造化出力は、この“文章生成エンジン”をシステム部品として扱える状態に近づけるための仕組みです。生成AIを単なるチャットではなく、業務システムとして構築するのであれば、構造化出力はほぼ不可欠な要素と言えます。
2026年2月、Amazon Bedrock にて構造化出力が利用可能になりました。これにより、JSON Schemaを指定してモデル出力を構造化データとして取得できます。
Amazon Bedrock で構造化出力が利用可能に
※ もちろん、OpenAI、Anthropic、Googleなどの生成AIでも構造化出力を使用することは出来ますが、今回はBedrockが対応したこともあり、Bedrockの紹介をします。
現時点では、すべてのモデルが対応しているわけではありません。
Novaを利用する場合は、公式ドキュメントで紹介されているプロンプトベース、もしくはツールベースの構造化出力手法を採用する必要があります。
つまり、
という使い分けが必要です。
以下は、BedrockのConverse APIでClaude系モデルを利用した例です。
import json
import boto3
from dotenv import load_dotenv
from pydantic import BaseModel, Field
load_dotenv()
class UserInfo(BaseModel):
name: str = Field(..., description="氏名")
age: int = Field(..., description="年齢")
def main():
session = boto3.Session()
bedrock_runtime_client = session.client(
"bedrock-runtime", region_name="ap-northeast-1"
)
request_dict = UserInfo.model_json_schema()
request_dict["additionalProperties"] = False # additionalProperties項目の設定は必須
res = bedrock_runtime_client.converse(
modelId="jp.anthropic.claude-haiku-4-5-20251001-v1:0",
messages=[
{
"role": "user",
"content": [{"text": "ダミーのユーザー情報をJSONで出力して。"}],
}
],
outputConfig={
"textFormat": {
"type": "json_schema",
"structure": {
"jsonSchema": {
"name": "user_info",
"schema": json.dumps(request_dict),
"description": "ユーザー情報",
}
},
}
},
)
output = res["output"]["message"]["content"][0]["text"]
user_info = UserInfo.model_validate_json(output)
print(user_info)
print(type(user_info))
if __name__ == "__main__":
main()
name='田中太郎' age=30
<class '__main__.UserInfo'>
これにより、
アプリケーションの型定義
→ JSON Schema
→ LLM出力
→ 型安全に受け取る
という流れが実現できます。
これは、生成AIを“実験ツール”から“システムコンポーネント”へと引き上げる重要なステップです。
構造化出力の価値は、「JSONがきれいに返ること」そのものではありません。実務においては、システム設計を安定させる点に本質があります。
ここでは、弊社がシステム作成で利用した代表的な活用パターンを紹介します。
RAG(Retrieval-Augmented Generation)では、単に回答を生成するだけでなく、
といった情報も扱いたくなります。構造化出力を利用すれば、これらを明示的な構造として取得できます。
※ ここでは、生成AIに生成させた信頼度が果たして信頼できるのか、といった議論はおいておきます。
from pydantic import BaseModel, Field
class RAGResponse(BaseModel):
answer: str = Field(..., description="回答")
source_ids: list[str] = Field(..., description="参考にした出典ID")
confidence: float = Field(..., description="信頼度")
{
"answer": "社内規程では〇〇と定められています。",
"source_ids": ["doc_123", "doc_456"],
"confidence": 0.82
}
このように構造化されていれば、
といった分岐処理を実装できます。自由形式での回答から情報を抽出する設計に比べ、実装と運用の安定性は大きく向上します。
弊社で作成した下記システムでは、この方式を応用して出典にリンクをつけて、ユーザーがファクトチェックを行いやすいようにしています。
RAG(検索拡張生成)と生成AIを活用したAIチャットボットの開発
生成AIを業務フローの一部として使う場合、分類器として使用するケースも少なくありません。
例えば、受信するメールの自動分類です。構造化出力を利用すれば、分類結果を明示的な値として取得できます。
from pydantic import BaseModel, Field
from typing import Literal
class ClassificationResult(BaseModel):
category: Literal["案件紹介メール", "人材紹介メール", "その他"] = Field(
..., description="メールの分類"
)
{
"category": "案件紹介メール"
}
Literal を使うことで、許可された値以外は型エラーになります。
これにより、案件紹介メールだったらその後処理を行う。人材紹介メールやその他のメールだったら処理をしない、などのワークフローを実現できます。
弊社で作成した下記システムでは、この方式を応用してメールを分類しています。
生成AIによる構造化出力とベクトル検索で、SES案件探索を効率化
構造化出力は非常に強力ですが、
といった前提は変わりません。
しかし、従来の「プロンプトで祈る」方式と比べると、安定性は大きく向上します。
生成AIは本質的に不確実なモデルです。それをシステムに組み込む以上、出力構造の制御は避けて通れません。
構造化出力は、
ための重要な仕組みです。生成AIを“チャットツール”として使う段階であれば必須ではないかもしれません。
しかし、業務システムの一部として設計するのであれば、構造化出力はすでに標準的な構成要素のひとつと言ってよいでしょう。
もはや「あると便利な機能」ではなく、生成AIを安全に組み込むための前提条件に近い存在になっています。
Tech Funでは、お客様のフェーズに合わせ、生成AI活用に向けた支援を3つのパックでご提供しています。
生成AIに限らず、Web・業務システム開発やインフラ設計など、技術領域を問わずご相談を承っています。「何から始めれば良いか分からない」という段階でも構いませんので、ぜひお気軽にお問い合わせください。