廣瀬製紙株式会社

Employees' Blog

OpenAI API+Node.jsでJSON構造化出力をする
Structured OutputsとZod Response Format

公開日:2025.04.04 更新日:2025.04.04
なんらかの構造をイメージしたイラスト

こんにちは。廣瀬製紙株式会社 稼働率向上PJチームのA.Mです。

今回はOpenAI APIを使用する際のStructured Outputsについて、特にZodというTypescriptライブラリを使った構造化出力についてご紹介します。

特に、具体例として

  • ・SQLクエリを生成する場合
  • ・HTMLのテーブル構造を出力する場合

の2通りを紹介します。

以前においては、OpenAI APIを使ってJSONを出力する場合にはJSON Modeというものを使用するのが一般的でした。
ですが現在では、公式ドキュメントにはStructured Outputsが標準だと記載されています。

なぜZodを使うのか?

なぜZodを使うのか、というとそれは公式ドキュメントがZodを使っているからに他なりません。

私のStructured Outputsの使い方のイメージは、文字列としてJSON構造を渡すものだったのですが、あらためて公式ドキュメントをよく見てみるとZod Response Formatが~と書かれていたため、おや?と思いました。

おそらくこれまでPythonで使うことが多かったため、Node.jsの場合の公式ドキュメントを見る機会がなかったのでしょう。

構造化出力(Structured Outputs)とは何か?

従来、AIモデルからの回答をプログラムで扱う際は「JSONで答えて」と指示してもフォーマットが一定しなかったり、出力を正規表現でチェックする必要があったりと、扱いづらい面がありました。

その後、JSON Modeが出てきて安定してJSONが受け取れるようになり、現在ではまた一歩進んでStructured Outputsが標準のような感じになっています(たぶん)。

Structured Outputsを使えば、指定したJSONスキーマに従った回答を確実に受け取れます。
Node.jsのSDKではzodResponseFormatヘルパーを使用することで、Zodで書いたスキーマを渡すだけで対応するJSON Schemaに変換され、型安全に結果を扱えるようになります。

Zodとは何か?

Zodは、TypeScriptで書かれた型検証ライブラリです。主な特徴として:

  • ・宣言的なスキーマ定義が可能で、直感的なAPIを提供
  • ・TypeScriptの型推論と連携し、バリデーション後の型安全性を保証
  • ・オブジェクト、配列、プリミティブ型など、様々なデータ構造の検証に対応
  • ・カスタムバリデーションルールを簡単に追加できる柔軟性

特にOpenAIのStructured Outputs機能と組み合わせる場合、ZodスキーマはJSON Schemaに変換され、AIモデルの出力を強制的に指定した形式に従わせることができます。
これにより、「型の不一致」や「必須フィールドの欠落」といった問題を未然に防げます。

実装例1: 自然言語からSQLクエリを生成する

最近の私の取り組みとして、Anthropicが提唱したModel Context Protocolを利用し、ReactのクライアントからPostgreSQLデータベースの情報を取得するという試みをよく行っています(別記事にて紹介します)。
そのため、SQLクエリを生成AIの出力結果として受け取る機会が増えました。

そのような想定で、実装例を見てみましょう。

なおZod自体はSQLの構造化には対応していないので、あくまでSQLクエリを文字列として受け取る、という対応になります。
ですが今のところうまくSQLクエリを受けとれており、失敗することはありません。

それではユーザーの質問文からSQLクエリを自動生成する例を見てみましょう。

import { z } from "zod";
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";

// SQLクエリ用のスキーマ定義
const SqlQuerySchema = z.object({
  query: z.string().describe("生成されたSQLクエリ文字列")
});

const api_key = "xxx";

const openai = new OpenAI({
  apiKey: api_key,
});

// API呼び出し
const completion = await openai.beta.chat.completions.parse({
  model: "gpt-4o-2024-08-06",
  messages: [
    { role: "system", content: "あなたはデータベースアシスタントです。ユーザーの質問に対応するSQLクエリのみを返してください。" },
    { role: "user", content: "年齢が18歳以上のユーザーの名前と年齢を一覧表示するSQLは?" }
  ],
  response_format: zodResponseFormat(SqlQuerySchema, "SqlQuery")
});

// 結果の取得
const result = completion.choices[0].message.parsed;
console.log(result.query);

このように、モデルからの回答が常に{ query: “SQLクエリ文” }という形で返ってくるため、パースやエラー処理の手間が大幅に削減されます。

実装例2: 表形式データをHTMLテーブルで表示する

次に、構造化データをHTMLのテーブルとして表示する例を紹介します。

import { z } from "zod";
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";

const TableSchema = z.object({
  headers: z.array(z.string()).describe("テーブルの列見出し"),
  rows: z.array(z.array(z.string())).describe("テーブルの各行データ")
});

const api_key = "xxx";

const openai = new OpenAI({
  apiKey: api_key,
});

// API呼び出し
const completion = await openai.beta.chat.completions.parse({
  model: "gpt-4o-2024-08-06",
  messages: [
    { role: "system", content: "あなたは有用なデータアシスタントです。要求された情報を表形式でJSON出力してください。" },
    { role: "user", content: "代表的なプログラミング言語とそれぞれの初登場年を教えて" }
  ],
  response_format: zodResponseFormat(TableSchema, "TableData")
});

// 結果の取得
const tableData = completion.choices[0].message.parsed;

console.log(tableData);

モデルからは以下のような構造化データが返ってきます:

{
  headers: [ 'プログラミング言語', '初登場年' ],
  rows: [
    [ 'Fortran', '1957' ],
    [ 'Lisp', '1958' ],
    [ 'COBOL', '1959' ],
    [ 'BASIC', '1964' ],
    [ 'C', '1972' ],
    [ 'C++', '1985' ],
    [ 'Python', '1991' ],
    [ 'Java', '1995' ],
    [ 'JavaScript', '1995' ],
    [ 'Ruby', '1995' ],
    [ 'PHP', '1995' ],
    [ 'C#', '2000' ]
  ]
}

これをHTMLテーブルに変換するコードも簡単です:

// HTMLテーブル生成
let html = "<table>\n  <tr>";
for (const headerText of tableData.headers) {
  html += `<th>${headerText}</th>`;
}
html += "</tr>\n";
for (const row of tableData.rows) {
  html += "  <tr>";
  for (const cell of row) {
    html += `<td>${cell}</td>`;
  }
  html += "</tr>\n";
}
html += "</table>";

実装上の注意点

構造化出力機能を使う際にはいくつか制約があります:

  • ・ネストは最大5階層まで
  • ・オブジェクトのプロパティは最大100個まで
  • ・一部の高度なJSON Schema表現は未対応

また、フィールドには可能な限りdescribe()で説明を付けると、モデルが正しく理解しやすくなります。

まとめ

OpenAIの構造化出力機能とZodを組み合わせることで、AIモデルの出力を安全かつ効率的に扱えるようになります。
SQLクエリの生成やデータの表形式出力など、様々なユースケースで活用できるため、業務システムへのAI組み込みが格段に容易になりました。

今後も当社では、こうした技術を活用して業務効率化を進めていきます。

皆さんもぜひ、自分の業務に合わせたスキーマを設計して、ChatGPTの応答を有効活用してみてください!