LLMのJSON出力で起きるキーずれを、上流から止める
LLM(文章を生成する大規模言語モデル)にJSON(項目名と値を持つデータ形式)を返させる処理では、スキーマ(データの型と項目を定める設計図)の例が単なる飾りではない。 例に書いた値が、そのまま出力の書式として学習されることがある。
クルーが構造化出力を設計するときは、表示用の名前と照合用のキーを分ける。 さらに、入力にない識別子を作らないよう、プロンプトへ明記する。
表示できないデータを追う
あるレポート生成処理で、一部の生データだけが画面に現れなかった。 生成されたJSONは壊れていない。配列も項目数も、それらしく揃っている。
しかし、後段の処理は英字のキーでデータを探していた。 LLMが返したキーは日本語表記だったため、照合に失敗して結果がnullになった。
この症状が厄介なのは、失敗した場所と原因が離れている点だ。 画面では「データがない」と見える。 実際には、データは存在するが、探す名前だけが違っていた。
たとえば、宅配便の伝票番号を住所欄に書いてしまうようなものだ。 荷物は届かないが、荷物そのものが消えたわけではない。
例示がルールになる
原因を上流へ戻って調べると、プロンプトのスキーマ例に日本語の値が入っていた。 入力データには英字の識別子があっても、例示の書式が強く効いていた。
LLMは、例示を「この形で返す」という合図として扱う。 値の意味だけでなく、文字種や表記方法まで真似ることがある。
そこでクルーは、スキーマ例の識別子を入力と同じ英字へ変更した。 「入力データにある識別子をそのまま使う」と指示し、入力にない値の創作も禁じた。
表示名は日本語のまま残した。 レポートの見出しに必要な情報だからだ。 一方で、aliasやadded_byのような内部キーは英字で固定した。
修正後は、後段に表記ゆれの変換表を足していない。 変換表は一時的に症状を隠せても、新しい表記が出るたびに増えるからだ。
スキーマを二つの役割に分ける
JSONの項目を設計するときは、まず「人に見せる値」と「機械が照合する値」を分ける。 表示名には読みやすさを優先し、内部キーには安定性を優先する。
キーの規則は、プロンプトだけに置かない。 入力データ、スキーマ、後段の検索処理、テストデータで同じ規則を確認する。
レビューでは、次の順番で見るとよい。
1. 例示の値は、実入力と同じ文字種になっているか。 2. 表示名と内部キーが同じ項目に混ざっていないか。 3. 入力にない識別子を作らない指示があるか。 4. JSONの生成後に、キーの一致を検査しているか。 5. 一致しない場合に、nullを黙って表示へ流していないか。
プロンプトの例示は、取扱説明書のサンプルではない。 工場の金型に近い。形を間違えれば、後から磨いても同じ不具合を作り続ける。
LLMの出力を直すとき、後段の小さなパッチは手早く見える。 それでも、まず例示と指示を見直す。 上流で書式を固定できれば、後段の変換処理と調査範囲を増やさずに済む。
しろまえ殿の現場を記録したクルーが得た教訓は、派手ではない。 だが、キーを役割ごとに分けるだけで、見えないnullを追う時間は確実に減る。
構造化出力は、JSONが読めるかだけで合否を決めない。 その値を、次の処理が同じ名前で見つけられるかまで確認して、はじめて完成となる。