生成AIの出力をJSON Schemaで固定する方法|構造化出力を自動化へつなぐ設計

生成AIを文章作成だけに使っている間は、出力形式が多少変化しても大きな問題にはなりません。

しかし、AIの出力をPython、データベース、Webアプリ、CAD、自動投稿システムなどへ渡そうとすると事情が変わります。

たとえばAIへ、

「部品情報をJSONで出力してください」

と指示したとしても、毎回まったく同じ構造になるとは限りません。

{
  "name": "hydraulic_cylinder",
  "bore_mm": 100,
  "stroke_mm": 500
}

と返ることもあれば、

{
  "component": "hydraulic_cylinder",
  "dimensions": {
    "bore": 100,
    "stroke": 500
  }
}

となる可能性もあります。

人間が読むだけなら、どちらでも意味は理解できます。

しかしプログラムから見れば、これは異なるデータ構造です。

そこで重要になるのがJSON Schemaと構造化出力です。

目次

JSONを出力させるだけでは自動化には不十分

JSONは構造化データを表現する形式です。

しかし、

JSONである

ことと、

期待したデータ構造である

ことは別問題です。

たとえば次の2つは、どちらもJSONとして有効です。

{
  "bore_mm": 100
}
{
  "diameter": "100 mm"
}

しかし、Python側が、

bore = data["bore_mm"]

としている場合、2番目のJSONでは処理できません。

さらに、

{
  "bore_mm": "one hundred"
}

もJSONとしては有効です。

つまり、JSONだけでは、

  • キー名
  • データ型
  • 必須項目
  • 許容値
  • 入れ子構造
  • 追加項目の可否

などを十分に固定できません。

自動化で必要なのは単なるJSONではなく、機械側が期待している構造を定義することです。

JSON Schemaとは

JSON Schemaは、JSONデータがどのような構造を持つべきかを記述するための仕組みです。

たとえばシリンダ情報を次のように定義できます。

{
  "type": "object",
  "properties": {
    "component_id": {
      "type": "string"
    },
    "bore_mm": {
      "type": "number"
    },
    "rod_mm": {
      "type": "number"
    },
    "stroke_mm": {
      "type": "number"
    }
  },
  "required": [
    "component_id",
    "bore_mm",
    "rod_mm",
    "stroke_mm"
  ],
  "additionalProperties": false
}

ここでは、

component_id → string
bore_mm      → number
rod_mm       → number
stroke_mm    → number

という型を指定しています。

さらにrequiredによって必須項目を指定し、additionalProperties: falseによって定義していないプロパティを許可しない構造にしています。

JSON Schemaではpropertiesで各プロパティのスキーマを定義でき、requiredで必須プロパティを指定できます。またadditionalPropertiesをfalseにすると、定義外のプロパティを禁止できます。

JSON Schemaがあると何が変わるのか

通常のAI出力では、

自然言語
↓
AI
↓
自然言語または不定形データ

となります。

JSON Schemaを境界として使うと、

自然言語
↓
AI
↓
定義済みJSON構造
↓
Python
↓
DB
↓
Webアプリ・CAD・計算処理

というパイプラインを設計できます。

つまりJSON Schemaは、単なる出力フォーマットではなく、AIと従来型プログラムの間のインターフェース仕様として利用できます。

Structured OutputsとJSON modeは違う

生成AI APIを使う場合、この違いは重要です。

OpenAIの公式資料では、旧来のJSON modeは有効なJSONを生成するための機能ですが、指定したJSON Schemaに適合することまでは保証しません。

一方、Structured Outputsでは開発者が指定したJSON Schemaに出力を適合させることを目的としています。現在のAPIリファレンスでも、対応モデルでは旧JSON modeよりJSON SchemaによるStructured Outputsが推奨されています。

したがって、

JSONとして読めればよい

場合と、

後段プログラムへ確実に渡したい

場合を分けて考える必要があります。

後者ではスキーマを定義した構造化出力が重要になります。

工学データでは単位まで設計する

工学用途では、データ型だけでは不十分です。

たとえば、

{
  "pressure": 14,
  "flow": 40
}

というデータがあっても、

  • pressureはMPaなのかbarなのか
  • flowはL/minなのかm³/sなのか

が分かりません。

方法の一つは、キー名に単位を含めることです。

{
  "pressure_mpa": 14,
  "flow_l_min": 40
}

さらに汎用的なデータモデルにするなら、

{
  "pressure": {
    "value": 14,
    "unit": "MPa"
  },
  "flow": {
    "value": 40,
    "unit": "L/min"
  }
}

と分離する方法もあります。

どちらが正しいというより、用途に応じて選択します。

小規模な計算ツールならpressure_mpaのような形式は単純です。

複数単位を扱う共通基盤なら、valueとunitを分離した方が単位変換処理へ発展させやすくなります。

重要なのは、AIに単位を推測させないデータ構造にすることです。

AIに計算させる部分とPythonに計算させる部分を分離する

構造化出力を導入すると、AIと通常プログラムの役割分担も明確になります。

たとえばユーザーが、

「内径100 mm、ロッド径56 mm、ストローク500 mmのシリンダ」

と入力したとします。

AIには文章からパラメータを抽出させます。

{
  "bore_mm": 100,
  "rod_mm": 56,
  "stroke_mm": 500
}

その後の断面積計算はPython側で行います。

ピストン側受圧面積は、A1=πD24A_1 = \frac{\pi D^2}{4}

ロッド側有効受圧面積は、A2=π(D2−d2)4A_2 = \frac{\pi(D^2-d^2)}{4}

です。

ここで、

  • DD:シリンダ内径
  • dd:ロッド径
  • A1A_1:ピストン側受圧面積
  • A2A_2:ロッド側有効受圧面積

です。

このような確定的な計算は、AIに毎回答えを生成させる必要がありません。

自然言語の解釈
        ↓
       AI
        ↓
構造化パラメータ
        ↓
     Python
        ↓
確定的な計算結果

と分離できます。

この構造にすると、同じ入力に対して同じ計算ロジックを適用でき、単体テストも可能になります。

スキーマと計算ロジックを分離する

さらに再利用性を高めるなら、

schema/
    cylinder.schema.json

logic/
    cylinder.py

data/
    cylinder-001.json

のように分離できます。

cylinder.schema.jsonはデータ仕様を担当します。

cylinder.pyは計算を担当します。

cylinder-001.jsonは個別データです。

こうすると、

データ定義
計算ロジック
実データ

が混ざりません。

将来Webアプリへ移行しても、CAD生成へ利用しても、同じデータモデルを再利用できます。

AI出力はスキーマ適合だけで信用しない

ここは非常に重要です。

Structured Outputsによってスキーマに適合したJSONが得られても、値そのものが工学的に正しいことまで保証されるわけではありません。

OpenAIの公式資料でも、Structured Outputsは構造への適合を改善する一方、JSON内部の値そのものの誤りまですべて防ぐものではないと説明されています。

たとえば、

{
  "bore_mm": -100,
  "rod_mm": 120
}

がデータ型として数値であっても、

内径 = -100 mm
ロッド径 > 内径

は機械設計上おかしな入力です。

したがって実用システムでは、

AI
↓
JSON Schema
↓
アプリケーション検証
↓
計算

という多段構造が必要です。

Pythonなら概念的には、

def validate_cylinder(data):
    bore = data["bore_mm"]
    rod = data["rod_mm"]
    stroke = data["stroke_mm"]

    if bore <= 0:
        raise ValueError("bore_mm must be positive")

    if rod <= 0:
        raise ValueError("rod_mm must be positive")

    if rod >= bore:
        raise ValueError("rod_mm must be smaller than bore_mm")

    if stroke <= 0:
        raise ValueError("stroke_mm must be positive")

のようなルールを追加できます。

これはJSON Schemaによる構造検証と、設計ルールによる意味検証の分離です。

設計ルールを三層に分ける

工学システムでは、検証を三層に分けると整理しやすくなります。

構文
↓
構造
↓
意味

構文レベルでは「JSONとして正しいか」を確認します。

構造レベルでは、

bore_mmが存在するか
number型か
余計なキーがないか

などをJSON Schemaで確認します。

意味レベルでは、

bore_mm > 0
rod_mm < bore_mm
使用圧力が対象機器の許容範囲内か

などをアプリケーションの設計ルールとして確認します。

この三つを混同しないことが重要です。

RDBへ保存する場合にもJSON Schemaは無駄にならない

最終的にデータをRDBへ保存する場合でも、JSON Schemaは中間インターフェースとして利用できます。

たとえば、

ユーザー入力
↓
AI
↓
JSON
↓
Schema検証
↓
設計ルール検証
↓
RDB

という構成です。

RDBでは、

  • PRIMARY KEY
  • FOREIGN KEY
  • UNIQUE
  • NOT NULL
  • CHECK
  • TRANSACTION

などによって、保存データの整合性を管理できます。

一方JSONは、AI、API、Python、Webフロントエンドなどの間でデータを受け渡す形式として使えます。

つまり、

JSON Schema = インターフェース仕様
RDB Schema  = 永続化仕様
Python      = 計算・検証ロジック

と役割を分離できます。

JSONとRDBは競合するものではなく、組み合わせて使うことで強力になります。

スキーマにはバージョンを持たせる

長期間利用するデータでは、スキーマ変更への対応も必要です。

最初は、

{
  "bore_mm": 100,
  "rod_mm": 56
}

だけだったものに、後からストロークを追加するかもしれません。

そこで、

{
  "schema_version": "1.0",
  "bore_mm": 100,
  "rod_mm": 56,
  "stroke_mm": 500
}

のようにバージョン情報を持たせる方法があります。

するとPython側で、

version = data["schema_version"]

if version == "1.0":
    ...
elif version == "2.0":
    ...

のような移行処理を設計できます。

データを長期的な資産として扱う場合、スキーマ変更を前提にしておくことは重要です。

AIに自由文を書かせる部分も残す

すべてを細かなフィールドへ分解すればよいわけではありません。

たとえば設計レビュー結果なら、

{
  "status": "warning",
  "rule_id": "CYL-002",
  "message": "ロッド径と内径の関係を確認してください",
  "details": "..."
}

のように、

status
rule_id

は厳密な構造として管理し、

message
details

は人間向けの文章としてAIに生成させる設計も可能です。

つまり、

機械が利用する部分は厳密に、人間が読む部分は柔軟に

という境界を作ります。

これが生成AIを実務システムへ組み込む際の重要な設計原則です。

構造化出力から自動化へ発展させる

最初は小さなJSON Schemaから始めるのが安全です。

たとえば、

自然言語
↓
AI
↓
3項目のJSON

だけでも構いません。

それが安定したら、

JSON Schema
↓
Python検証
↓
計算

へ進めます。

さらに、

AI
↓
JSON Schema
↓
Python
↓
RDB
↓
Web API
↓
Webツール

へ発展させられます。

設計分野なら、その先に、

機器選定
CADパラメータ生成
DXF生成
設計チェック
帳票生成

などを接続できます。

このとき重要なのは、AIそのものをシステムの中心に置くことではありません。

中心に置くべきなのは、

データモデル
スキーマ
設計ルール
計算ロジック

です。

AIは自然言語と構造化データの境界を担当する一つのコンポーネントとして扱えます。

まとめ

生成AIを自動化へ組み込む場合、「JSONで出力してください」というプロンプトだけでは十分ではありません。

必要なのは、

AI
↓
構造化出力
↓
JSON Schema
↓
意味検証
↓
計算ロジック
↓
DB・Web・CAD

という境界設計です。

JSON Schemaを使えば、キー名、データ型、必須項目、追加項目などを明示できます。

しかしスキーマに適合していることと、工学的に正しいことは別です。

そのため、

JSONとして正しい
↓
Schemaとして正しい
↓
設計条件として正しい

という段階的な検証が必要になります。

この構造を作っておけば、生成AIを変更しても、Python、データベース、Webアプリ、CADなど後段のシステムを比較的独立して維持できます。

生成AIを単なる文章生成ツールから実務システムの入力装置へ発展させるうえで、JSON Schemaによる構造化は重要な基礎技術になります。

参考情報

  • OpenAI「Structured Outputs」
  • OpenAI API Reference「JSON Schema response format」
  • JSON Schema公式ドキュメント「Objects / Properties / Required / Additional Properties」
参考になったらシェアしてください
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

機械設計・油圧・CAD・Python・AIなど、ものづくりに関わる技術を扱っています。工学知識を整理・構造化し、設計や自動化に再利用できる形へ変えていくことを目指しています。

目次