Dekita

AI は仕様を知っているのに既定のエラーを書く。RFC 9457 を規程に書き下す 4 項目

生成AI API 日本語 AIエージェント

はじめに

この稿が答えるのは、日本のチームが今週抱えそうな質問です。規程書に「エラーレスポンスの形式を統一する」と書いてあるのに、AI が作った API はエンドポイントごとに別の形のエラーを返している。では、その規程の項目をどう書けばよいのか。

2026 年 11 月 12 日に Zenn で公開された検証記事(tkydev 氏、以下「検証記事」)は、この問題の実験データを持っています。仕様を正しく説明できる同じ AI が、実際に作らせるとフレームワークの既定のエラーを書く。本篇は、規程を作る側が「統一する」を 4 項目に書き下す型を、その実験結果から整理します。

背景:知っていることと書くことの差

検証記事の出発点は、mattn 氏の X 上のポストです。ユーザーが見つからないときのエラーを 2 形で並べ、どちらを承認するかを問うものです。設計 A は 200 OK を返し、本文の success フラグで失敗を伝えます。設計 B は 404 Not Found を返し、本文にメッセージを入れます。ステータスコードの使い方としては B が正しい。mattn 氏の指摘は、B でも厳密には足りない、というものです。足りないのは標準のメディアタイプと本文の形です。

RFC 9457(HTTP API の問題詳細を定義する IETF 標準)は 2023 年 7 月に公開され、2016 年の RFC 7807 を置き換えました。エラー本文の 5 つのメンバーと、application/problem+json というメディアタイプを定義しています。5 つのメンバーは type や title や status や detail や instance です。type は問題の種類を識別し、detail はその発生に関する説明を運びます。この分離が重要です。クライアントは type で分岐し、人は detail を読みます。

検証記事は 2 つのモデルと 4 つのフレームワークで実験しています。Claude と ChatGPT、Express、FastAPI、Spring Boot、ASP.NET Core です。回数を重ねる前に毎回新しい会話にし、メモリやカスタム指示の影響を排しています。

実験:説明はできる、書くは別の話

「RFC 9457 を説明してください」への答えは、2 つのモデルとも正しかったです。5 つのメンバー、about:blank の意味、本文の status を実際のステータスと一致させる要件まで、内容は合っていました。

「設計 A と B のどちらを承認しますか」への答えは分かれました。Claude は 2 回とも B はそのままでは承認しないと答え、RFC 9457 形式への書き直しを示しました。ChatGPT は 2 回とも B を承認し、RFC 9457 には触れませんでした。

問題は作らせるときです。「{フレームワーク} でユーザーを ID で取得する REST API を実装してください」を、4 フレームワーク × 3 回 × 2 モデルで実行しました。生成コードは Docker で起動し、4 種のリクエストを送っています。4 種は、存在しない ID の取得、形式違いの ID、定義されていないパス、定義されていないメソッドです。

結果は 24 回中 5 回です。RFC 9457 の形式、つまり 404 と application/problem+json を返したのは 5 件のみでした。Claude の Spring Boot 3 回と ASP.NET Core 2 回です。ChatGPT は 12 回とも使っていません。レビューでは C を勧めた Claude が、Express では独自形式を 3 回とも書きました。

フレームワーク既定が勝つ理由

AI が書くのは仕様ではなく、そのフレームワークで書き慣れた形です。FastAPI の 6 回はすべて HTTPException を投げるだけで、それが {"detail": ...} の形になるため、6 回とも同じ形でした。

ASP.NET Core は分かれます。引数なしの NotFound() は ProblemDetails に変換されますが、メッセージを渡すと変換の対象外になり、渡したオブジェクトがそのまま本文になります。つまり「親切にメッセージを足す」ことが、標準の形から外す方向に働きます。Spring Boot は、RFC 9457 形式を有効にする spring.mvc.problemdetails.enabled が既定でオフです。

検証記事の中で最も注意を引いたのは、本文とヘッダが割れている 1 件です。ASP.NET Core の 1 回は、本文は RFC 9457 の形なのに Content-Type が application/json でした。コードを読む限りでは適合していると判定した形です。実際に起動して初めて分かりました。原因は、.NET 10 の TypedResults.NotFound() が値を受け取ると空のメンバーを埋め、メディアタイプを指定しない点です。JSON の既定である application/json になります。この挙動は 2024 年 10 月に Issue #58574 として報告されています。.NET 11(2026 年 11 月 10 日公開)では、PR #66461 で application/problem+json で書き出すように変わります。検証記事はソースコードの確認でそう述べています。ただし .NET 11 では起動していません。

ここから得る教訓は、AI が書いた API のレビューで「コードを読めばよい」が通用しない、ということです。実際にリクエストを送り、Content-Type まで確かめるのが確実な検証です。

規程への書き下し:4 項目

検証記事の追加実験では、プロンプトに「エラーレスポンスは RFC 9457 に従ってください」の 1 行を足した 6 回が、6 回とも標準の形になりました。404 だけでなく、フレームワークが自動で返すエラーも対象です。FastAPI の 422 検証エラーや未定義メソッドの 405 まで、指示した 6 回とも標準の形でした。足した 1 行には application/problem+json もメンバー名も書いていません。最も短い形で、AGENTS.md や CLAUDE.md にそのまま書ける指示です。

この結果から、規程の型として 4 項目が引けます。

  1. AI 向けの規則ファイルに指示を 1 行書く。「エラーレスポンスは RFC 9457 に従ってください」を最小とする。
  2. フレームワークの設定で済むものは設定にする。Spring Boot は spring.mvc.problemdetails.enabled=true を設定する。ASP.NET Core は AddProblemDetails() と UseStatusCodePages() を登録する。Express と FastAPI は組み込みの仕組みが無く、エラーハンドラーを自前で書く。
  3. type を管理する。type が about:blank のままだと、ステータスコード以上の情報を持たず、問題ごとに対応を分けられない。対応を分けたい種類には独自の type URI を定義し、文書化する。
  4. 検証手順を「起動してリクエストを送る」にする。コードの読解は Content-Type の不一致を見逃す。確認するリクエストは、存在しない ID、形式違いの ID、未定義パス、未定義メソッドの 4 種を揃える。

項目 1 と 2 は方向が異なります。1 は AI に書いてもらうための指示、2 はフレームワーク自身が出力するための設定です。2 がある場合は AI が関与しない API にも効きます。項目 3 はクライアント側の契約、項目 4 は検証の関門です。

まとめ

この検証のギャップは、AI が仕様を知らないことではありません。2 つのモデルとも正しく説明し、Claude はレビューでその形式を勧めます。欠けているのは「既定で使う」という一文の置き場所です。書きの段階ではフレームワークの既定が勝ち、コードを読む限りは適合しているように見えます。

規程書の「エラーレスポンスの形式を統一する」は、上 4 項目を自チームのフレームワーク名で埋めた形に書き下してください。指示 1 行と検証手順が規則に入っていれば、「AI が別の形を使った」は信任の問題ではなく、レビューで指摘できる問題になります。

参考情報

本稿は AI の支援を受けて執筆されました。事実の確認と内容の責任は筆者にあります。