DifyのLLMノードで構造化出力が失敗したら、JSONとして読めるか、約束した項目と型があるか、元の問い合わせと内容が合うかを分けて確認します。「JSONで答えて」と書き足すだけで、どの段階の問題も解決できるわけではありません。
対象は、DifyのワークフローでLLM出力を次のノードへ渡したい人です。編集部作成の架空問い合わせを使い、8種類の出力をPythonで照合します。Dify実機や有料APIで測った成功率ではなく、固定データを使ってエラーの位置を理解するローカル教材です。
確認日:2026年10月14日。Difyの現行LLMノード資料とCodeノード資料、JSON Schemaのobject仕様、PythonのJSON仕様を確認しました。掲載コードはPython 3.12.14で実行しています。Difyのモデルや導入版ごとの挙動を、このローカル実行だけで確認したとは表現しません。
LLMのJSON出力を、構文とスキーマの問題に分ける
JSONとして読めることと、必要な内容があることを分ける
JSONは、項目名と値を一定の書式で表す形式です。波括弧が見えたからJSONとして読めるとは限りません。末尾に余分なカンマがある、引用符が閉じていない、前後に説明文が付いているなど、解析の段階で止まる出力もあります。
一方、JSONとして読めても、sessionsという必要な項目がなければ、研修回数を次へ渡せません。sessionsが文字列の“2”なら、数値の2を期待する処理とは異なります。さらに数値の3が入っていても、元の案内が2回なら内容が違います。
この三つを同じ「JSONエラー」と呼ぶと、直す場所を見失います。構文が壊れた出力は生成形式や受け取る本文を確認し、項目や型が違う出力はスキーマと変数を確認し、内容が違う出力は原文と抽出の条件へ戻ります。
本稿では、parse、shape、meaningという三つの検収結果を教材内で使います。Difyがこの三分類のエラー名を標準で表示するという意味ではありません。どこまで確認できたかを、学習者が自分の言葉で説明するための分類です。
エラー画面を記録するときは、ノード名、入力、出力、期待した項目、実際に止まった箇所を揃えます。画面の赤い表示だけを見てモデルを変更すると、下流の変数参照が間違っていた問題を残す可能性があります。原因に合う変更を一つずつ行います。
外部APIへ送る本文の型が問題なら、DifyのHTTP RequestとJSON Bodyの型確認へ進めます。本稿は、HTTPの通信成功や送信先の処理ではなく、その前のLLM出力が何を意味するかを確認する段階です。
現行LLMノードの構造化出力と、モデルの対応を確認する
現行の公式LLM資料では、構造化出力をビジュアル編集、JSON Schema直接編集、AIによる生成の三方式で設定できると説明されています。自分の環境にどの編集方法があるかを確かめ、使うモデルと導入版を記録してから設定します。
公式は、ネイティブ対応のモデルと、スキーマをプロンプトへ含める方式を区別しています。後者は結果が異なる場合があるため、構造化出力の設定を入れたことだけで、すべてのモデルに同じ制約が適用されたとは考えません。
比較するときは、クラウドか自分で導入した環境か、モデルの提供元、モデル名、関連プラグインの版を控えます。同じDifyという名前でも、画面や対応が一致するとは限りません。古い解説のスイッチ名が見つからない場合も、現在の公式資料と導入版の違いを先に確認します。
AIで生成したスキーマも、確認せずに採用しません。今回ならticket_id、category、sessions、review_requiredの四項目が必要です。AIがemailやnameを追加しても、その項目は教材に必要ありません。欲しい情報を人が決めてから、スキーマの内容を点検します。
複雑なスキーマを最初から使うと、モデルの対応問題と設計の問題が混ざりやすくなります。まず一つのObjectと少数の項目で確認し、必要になった段階で配列や入れ子を増やします。本稿も、問い合わせ一件を表す四項目に限定しています。
モデル変更によって出力が通る場合でも、内容の検収は残します。書式を満たすモデルでも、元の案内にない回数や受付IDを返す出力は、今回の期待値と合いません。構造化設定と、人が決めた内容の確認は別の役割を持ちます。
| 方法 | 使い始める場面 | 採用前の確認 |
|---|---|---|
| ビジュアル編集 | 少数の項目と型を整理する | 必須項目、値の型、説明が今回の目的に合うか |
| JSON Schema直接編集 | 項目の約束を明示して保存する | 構文、required、選択肢、追加項目の扱い |
| AIによる生成 | たたき台を作る | 不要な項目や推測を除き、期待値と照合する |
required・enum・nullを、架空の問い合わせ四項目で決める
教材の問い合わせは次の一件です。「受付IDはJ01。案内には研修が2回とある。開催日について質問したい。担当者による確認が必要」。これは編集部が作った入力で、実在の顧客やスクールの案内ではありません。
期待する出力は、ticket_idがJ01、categoryがschedule、sessionsが数値の2、review_requiredがBooleanのtrueです。この期待値は、モデルへ質問した後の結果から逆算して作りません。元の教材を読んで、人が先に決めます。
requiredは必要な項目を指定するものです。項目があることと、値が判明していることは別です。本稿では回数が不明な場合もsessionsという項目は残し、nullを使える形にします。空文字、0、nullを同じ意味として扱わないためです。
categoryはschedule、fee、unknownの三つへ限定します。日本語の「開催日」を扱う入力でも、出力の分類名はscheduleに揃えるという教材上の約束です。その他、日程、日時などを自由に混ぜると、下流で必要な分岐を見落とす可能性があります。
enumへunknownを用意したことは、元の入力を調べずにunknownを選んでよいという意味ではありません。不明の場合の逃げ道を設けたうえで、明示された開催日の質問はscheduleと照合します。選択肢に入っていることと、今回の分類が適切なことを別に確認します。
回数が未記載なら、sessionsを0にする理由はありません。0は開催回数がゼロという値で、未記載は回数を確認できていない状態です。後工程で集計するときにも違いが出るため、値と不明状態を分けて設計してください。
{
"type": "object",
"properties": {
"ticket_id": {"type": "string", "description": "入力にある受付ID"},
"category": {"type": "string", "enum": ["schedule", "fee", "unknown"]},
"sessions": {"type": ["integer", "null"], "description": "明示された研修回数。不明はnull"},
"review_required": {"type": "boolean", "description": "人の確認が必要か"}
},
"required": ["ticket_id", "category", "sessions", "review_required"],
"additionalProperties": false
}
このスキーマは教材の約束を表す例です。各提供元で利用できるJSON Schemaの範囲は別途確認します。スキーマとして記述できる条件が、そのまま自分のモデル設定で利用できると確認したわけではありません。非対応なら条件を勝手に外さず、下流で何を検収するかを決め直します。
additionalPropertiesと値の型を、下流へ渡す前に点検する
JSON Schemaのpropertiesで四項目を書いただけでは、追加項目の扱いまで指定したことにはなりません。objectの公式資料では、additionalPropertiesをfalseにすると、定義したもの以外の項目を認めない形にできると説明されています。
本稿は、想定外のemailを付けたD06を保留にします。四項目へ勝手に追加したデータを、必要な情報として次へ渡さないためです。実際の業務で追加項目を許可するなら、誰が何に使う項目か、保存や送信が必要かを別途決めます。
PythonではBooleanが整数の仕組みと関係するため、単に整数かを調べる判定ではtrueを回数として認めてしまう場合があります。本稿の固定検収ではtype(value) is intを使い、sessionsがTrueのD05を保留にします。真偽値と回数は別の意味です。
review_requiredにも、文字列の“true”を入れません。Booleanのtrueは確認が必要という値、文字列のtrueは文字情報です。下流でどちらも同じだとみなす短い処理を置く前に、入力の型が設計どおりかを確認してください。
数値の2.0についても注意します。JSON Schemaの数値仕様では、2と2.0のような整数値の表現を区別しません。Pythonの値の型をそのまま同一視しないことが確認点です。本稿のローカルコードは、回数をPythonのintかNoneだけに限定する狭い検収です。2.0を保留するのはこの教材の方針で、汎用JSON Schema検証器の挙動を再現しているわけではありません。
Objectを期待する次のノードへ、JSON文字列をそのまま渡す問題も別です。本稿の関数は、入力が文字列ならjson.loadsで解析してから検収します。Difyで使う場合は、実際の出力変数の型を確認し、どの変数をCodeの入力へ割り当てるかを決めてください。
構造化出力の変数名や、その中の項目名は自分の画面で確認します。画面に表示された文章がJSONらしく見えても、下流が参照しているのが別のtext変数なら、想定と違う入力になる場合があります。線をつないだことと、使いたい値が渡ったことを分けて調べます。
構文エラーと、未完了・失敗の出力を区別して記録する
出力が途中で終わっている場合、最後の括弧を足すだけで採用しないでください。括弧を補えば解析できても、途中で抜けた項目や値がないかは別の確認です。生成の完了状態、ノードの実行結果、返った本文の長さなどを調べます。
説明文やコードフェンスが付いたJSON文字列にも、何を取り除くかの判断が必要です。本稿の検収は、前後の文章を削って都合のよい部分を抽出する機能を持ちません。入力が完全なJSON文字列かを確かめ、読めないものはparseとして保留します。
JSONのキーが二重に出た場合も、注意が必要です。標準の解析で最後の値が採用される形を、そのまま安全な入力検証と見なしません。本稿の8例は重複キーを扱わない限定教材です。実運用で許可しないなら、解析の時点で別の検査を追加してください。
ノード自体の呼び出し失敗と、呼び出し後に内容が違った状態を分けて記録します。前者で出力を得られなかったのに、固定の期待値を返して成功に見せると、何を確認したかを失います。後者では、出力があっても入力との不一致を残す必要があります。
再試行を増やす前に、同じ条件で繰り返してよい問題かを判断します。通信の一時的な失敗と、毎回同じスキーマで欠ける項目は、直す場所が違います。試行回数の増加が、意味の正しさの確認に代わるわけではありません。
実行ログの確認では、利用者の個人情報をそのまま教材や問い合わせへ貼らないでください。項目名、型、架空ID、短い再現例で説明できる範囲を取り出します。本稿のJ01も、そのための編集部作成IDです。
8種類の出力を検収し、内容が違うJSONを保留する
元の案内から期待値を作り、モデルの回答と別に保持する
期待値をモデルの回答そのものから作ると、誤った出力も正解に見えます。本稿では、案内に研修2回と書いてあることを人が読み、先にsessions=2と決めます。LLMが3と返しても、期待値を3へ合わせる変更はしません。
受付IDも、今回の入力にあるJ01を保持します。出力にJ02が入っていれば、文字列という型は正しくても今回の問い合わせではありません。別の実行の結果や例示のIDが混ざった可能性を調べるため、内容の検収で止めます。
一件ごとの期待値には、何を根拠に決めたかも残します。本稿なら「開催日について質問」という入力からscheduleを選び、「担当者による確認が必要」からtrueを決めます。文字列の内容を人が確認する工程と、コードで一致を調べる工程を分けます。
不明値を使う場合も、今回の根拠へ戻ります。回数の記載がない別の教材ならnullを期待できますが、2回と明示されたJ01にnullを返していたら、そのまま採用しません。スキーマ上nullを許可していても、明記された値を落としてよいわけではありません。
多くの案件で人が全部の期待値を書けるとは限りません。その場合は、IDの一致、選択肢、数値範囲、原文の該当箇所など、どこを機械確認し、どこを人が読むかを設計します。本稿の全項目一致は、原文が短く期待値を固定できる教材上の方法です。
根拠の資料が検索結果から来る構成なら、DifyのKnowledge Retrievalと空の結果の確認も参照してください。参照資料を得られたかと、抽出した四項目が合っているかは別の検収になります。
固定教材の検収コードで、構文・形・内容の順に止める
次の関数は、汎用のJSON Schema検証器ではありません。教材で使う四項目に限って、存在・型・選択肢・期待値の一致を検査します。追加ライブラリや外部APIを使わず、JSON文字列とPythonの辞書を同じ入口で比べるための例です。
最初に文字列なら解析し、次に辞書と四項目を確認します。その後にIDと分類、回数、確認要否の型を点検し、最後に今回のexpectedと比べます。形が合わない値を、先に内容判定へ渡さない順番になっています。
import json
required = {"ticket_id", "category", "sessions", "review_required"}
def inspect_output(value, expected):
if isinstance(value, str):
try:
value = json.loads(value)
except (json.JSONDecodeError, ValueError):
return {"ok": False, "stage": "parse", "reason": "JSON構文"}
if not isinstance(value, dict) or set(value) != required:
return {"ok": False, "stage": "shape", "reason": "必要項目または追加項目"}
if (not isinstance(value["ticket_id"], str)
or not isinstance(value["category"], str)
or value["category"] not in ("schedule", "fee", "unknown")):
return {"ok": False, "stage": "shape", "reason": "IDまたは分類の型・選択肢"}
if value["sessions"] is not None and type(value["sessions"]) is not int:
return {"ok": False, "stage": "shape", "reason": "回数の型"}
if type(value["review_required"]) is not bool:
return {"ok": False, "stage": "shape", "reason": "確認要否の型"}
if value != expected:
return {"ok": False, "stage": "meaning", "reason": "今回の期待値と不一致"}
return {"ok": True, "stage": "accepted", "reason": "教材の期待値と一致"}
expected = {
"ticket_id": "J01", "category": "schedule",
"sessions": 2, "review_required": True
}
print(inspect_output(expected, expected))
このコードは、文字列の数値を自動的に数値へ直しません。“2”を2へ変換して通すことより、型が約束と違っている事実を先に残す方針です。変換が必要な入力を扱うなら、変換前の値を保持し、どの条件で直すかを別に決めます。
また、文字列が空白だけでないか、回数が負になっていないかなど、ここに書いていない汎用条件までは検査していません。今回のexpectedとの全項目一致によって教材の違いを止めます。別の入力へ広げる際には、必要な条件を明示して作り直してください。
D01からD08を実行し、形式が合う誤りも止める
正常な例のD01だけを試しても、入力の取り違えを見つけられるかは分かりません。そこで、構文、項目、型、追加情報、ID、回数をそれぞれ変えた八つの出力を用意します。出力は手作りで、実際のLLMが生成した記録ではありません。
cases = [
("D01", expected, True),
("D02", '{"ticket_id": "J01",}', False),
("D03", {k: v for k, v in expected.items() if k != "sessions"}, False),
("D04", {**expected, "sessions": "2"}, False),
("D05", {**expected, "sessions": True}, False),
("D06", {**expected, "email": "example"}, False),
("D07", {**expected, "ticket_id": "J02"}, False),
("D08", {**expected, "sessions": 3}, False),
]
for ident, value, should_pass in cases:
result = inspect_output(value, expected)
assert result["ok"] == should_pass
print(ident, result)
| 出力 | 止まる段階 | 次に見る場所 |
|---|---|---|
| D01 四項目が期待値どおり | 採用 | 原文と期待値の対応を記録 |
| D02 末尾カンマ | parse | 返った本文とJSON構文 |
| D03 sessionsなし | shape | 必須項目と参照変数 |
| D04 回数が文字列 | shape | 数値と文字列の型 |
| D05 回数がBoolean | shape | 真偽値と回数の区別 |
| D06 emailを追加 | shape | 予定外の項目と追加の理由 |
| D07 別の受付ID | meaning | 今回の入力IDと実行の対応 |
| D08 回数が3 | meaning | 案内に明記された2回という内容 |
D07とD08は、四項目を持ち、値の型と選択肢も合っています。それでも今回の問い合わせには適合しません。見た目がきれいなJSONほど、後工程へそのまま渡してしまいがちなので、IDと原文の値を照合する工程を残します。
同じ関数に、sessionsが2.0の出力と、review_requiredが文字列のtrueの出力も渡して確認しました。どちらも本稿の狭い型判定ではshapeへ戻ります。さらにunknownとnullを期待値にした別の固定例では採用でき、不明値の入口も確認しました。
ただし、これらの負例が止まったことから、実際の業務のすべての誤りを見つけられるとは言えません。説明文の意味が微妙に変わる場合、複数資料の矛盾、誤った原資料などは、この四項目の教材とは別の確認が必要です。
Difyへ組み込むときは、入力変数と保留経路を確認する
公式Codeノード資料では、上流の値をInput Variablesで受け取り、宣言したOutput Variablesに対応する辞書を返す形が示されています。ローカル関数をそのまま置いただけで、画面の入出力設定まで済んだことにはなりません。
自分の環境へ取り入れる場合は、LLMのどの変数をvalueへ渡すか、入力からどの値をexpectedへ用意するかを確認します。今回のJ01や2回は教材の固定値です。実際の問い合わせへ常にJ01を割り当てる設計にしてはいけません。
Code側の返り値には、採用できたか、止まった段階、保留理由が含まれます。その型を定義し、採用できなかった場合は人が確認する経路へ分けます。失敗時に空の文字列や0回だけを返す設計では、未確認の出力が正常な情報へ見えかねません。
条件で経路を選ぶ段階は、DifyのIf/Elseと条件境界の確認を参照してください。結果がfalseという値なのか、項目がないのかを区別し、採用と保留を同じ出口へ混ぜないことが確認点です。
複数経路の結果を合流させるときは、変数集約器と分岐出力の確認も役立ちます。本稿の検収を追加しても、どの経路が実行されたかの確認は別に残ります。未実行の値と、実行済みで保留になった値は異なります。
表示だけを整えるTemplateへ、未検収のJSONを直接渡すことも避けます。文章になった後で項目の欠落を見つけるより、値の段階で止める方が原因を追いやすくなります。空欄の表示については、DifyのJinja2と空値の扱いで別途整理できます。
FAQと、問い合わせを学習教材へ変える確認票
確認票には、入力の短い原文、期待する四項目、モデルと導入版、スキーマ、実際に参照した変数、検収結果を残します。設定を変更したら、どこを変えたかを追記します。結果の良かった一件だけでなく、保留になった例も確認に使います。
学習の区切りとしては、D02とD08の違いを説明できるかを確かめます。前者はJSONとして読めない出力、後者は読めて型も合うが内容が違う出力です。この違いが分かると、何を再生成し、何を原文へ戻して調べるかを整理できます。
自分で進めやすい人は、入力と期待値を作り、型の違いを見ながら小さい確認例を増やせる人です。学習先を比較したい人は、画面の設定はできてもエラーの位置を説明できない人、変数の入力と出力を一緒に整理したい人です。
講座を探す際は、目的と経験別のスクール一覧からAIアプリ開発の入口を確認できます。Difyを扱う教材があっても、この検収コードの添削や個別業務のレビューが含まれるとは限りません。相談の範囲、前提知識、学習中に質問できる時間を申込前に確認してください。
2026年10月14日に確認したバイテックのDifyマスターコースには、Dify入門とワークフローを学ぶ案内があります。出力設定だけでなく、入力から後工程へ渡す流れを順に整理したい人の比較候補です。本稿のJSON Schema検収やPythonコードの添削が講座に含まれるとは確認していません。相談では、利用中のモデル、項目と型の例、どの段階で止まるかを伝え、学習範囲・質問対応・受講総額・別途ツール利用料を確認してください。
まず一つの出力だけを直したい人は、本稿の固定例と公式資料で検収を続ける方法もあります。フロー全体を基礎から学びたい人は、必要な支援範囲を説明してから講座と独学を比較できます。
PR:バイテックAIスクールの無料説明会・学習内容を確認する
無料説明会で内容を確認することと、有料講座へ申し込むことは別の判断です。自分の架空入力と検収結果を用意し、解決したい問題を整理してから比較してください。
Difyの構造化出力でよくある質問
Q. 構造化出力を有効にすれば、内容も正しくなりますか?
形を揃える設定と、元の入力との内容一致は別です。本稿のD07・D08は項目と型が合っていてもIDや回数が違うため、内容の検収で保留しました。
Q. JSONで出力すると書く方法と、JSON Schemaは同じですか?
文章による指示と、項目・型・選択肢などを定義するスキーマは区別します。現行Dify資料は、モデルのネイティブ対応とプロンプトへスキーマを含める方式も区別しています。自分のモデルと導入版を確認してください。
Q. requiredにした項目はnullにできませんか?
項目が存在することと、値が判明していることは別です。教材のsessionsは必須項目ですが、型にnullを許可します。ただし原文に2回と明記された今回の入力を、未記載のnullへ変えるのは期待値と異なります。
Q. 解析できたJSONを、そのまま次のノードへ渡せますか?
必要項目・型・選択肢と、今回の入力との一致を先に確認します。JSON文字列を期待するのかObjectを期待するのか、下流の入力変数の型も確認してください。
Q. 掲載コードはDifyのJSON Schema検証器ですか?
いいえ。固定四項目の存在・型・選択肢・期待値を調べるローカル教材です。汎用スキーマ検証器でもDify実機の検証結果でもありません。実際に組み込む場合は、入出力変数と保留経路を設定して自分の環境で確認します。