読者の疑問・今回の記事で分かること
「LLMノードから返ってきたJSONをn8n内でどう処理すれば良い?」と悩んでいませんか。この記事では、私が実際に自動化ワークフローを組んで試行錯誤した経験をもとに、安定して動く最小構成、Codeノードでの防御的な処理、よくあるエラーとその対処を具体例でまとめます。初心者でも再現しやすいステップを重視しました。
結論(要点を先に)
要点をざっくり言うと、自分が安定して使えている流れは次のとおりです。
- LLMには「JSONのみで返す」「説明やコードブロックを付けない」と明確に指示する
- それでも様々な形で来る(オブジェクト、文字列内JSON、“`jsonのフェンス付き)ため、Codeノードでフェンス除去→JSON.parseの防御実装を入れる
- Codeノードの返却は必ず配列で、jsonプロパティはオブジェクトにする(例:return [{ json: {…} }];)
- 元データ(row_number 等)は生成JSONとマージして返す設計にすると後工程が安定する
なぜこのテーマを試したか(短い背景)
自分はブログ記事の自動生成や、スプレッドシートの行ごとにAIに要約させるワークフローをn8nで作りたくて取り組みました。最初は「LLMにJSONで出力するように指示すればいいだろう」と思っていたのですが、実際にはかなり色々な形式で返ってきてハマりました。家事の合間にWorkflowのエラー通知を見ることが増え、不安定さを取るために時間をかけて対策を固めています。
具体的な最小ワークフロー(これで動作確認)
- Manual Trigger
- Set(入力データの用意。例:title、row_number)
- LLMノード(OpenAI/Gemini)— プロンプトでJSONのみ出力を指示
- Codeノード — フェンス除去・JSON.parse・元データマージを行う
- (以降)必要に応じてWP投稿作成やシート更新など
LLMノードのプロンプト設計で気をつけたこと
- 出力の形式を明確に伝える:可能ならStructured Output / JSON schema機能を使う
- 「JSONのみで返す」「余計な説明やコードブロックは出さない」と明示する(ただし、LLMは守らない場合がある)
- Retry On Fail(Max Tries 3–5、Wait Between Tries 10–30s)を設定して429/503に耐性を持たせる
Codeノードでの実務的な防御実装(私が使っているテンプレ)
実際にはLLM出力は次のパターンで来ます:オブジェクト、その中のcontent[0].textに文字列でJSONが入る、あるいは“`jsonのフェンス付き文字列。なのでCodeノードで全パターンに対応するようにしました。処理のポイントは「フェンスを除去してからJSON.parse」「try/catchでエラー時はrawを返して原因追跡を容易にする」ことです。
(読みやすさのために抜粋例を示します)
try {
const raw = $json.output?.[0]?.content?.[0]?.text || $json.text || $json;
if (!raw) {
return [{ json: { error: ‘no raw text’, original: $json } }];
}
const clean = (typeof raw === ‘string’ ? raw.replace(/“`json/g,”).replace(/“`/g,”).trim() : JSON.stringify(raw));
const data = JSON.parse(clean);
const row_number = $json.row_number || $(‘Get row(s)’).first().json.row_number;
return [{ json: { …data, row_number } }];
} catch (e) {
return [{ json: { error: e.message, raw: $json } }];
}
重要なのは、最後に return [{ json: { … } }]; の形で必ず配列を返す点です。jsonフィールドに配列を直接入れるとn8nがエラーを出します。
複数アイテム対応でハマった話(失敗談)
初めは複数行をまとめて処理する想定で$(‘Get row(s)’).first()を多用していました。表向きは動くのですが、実際は全ての出力が1件目のデータで上書きされるという問題に遭遇しました。原因はn8nのアイテム単位処理の仕組みを理解していなかったことです。
その後、$input.all() と map を使う設計に切り替え、各itemごとに元データを参照して処理することで解消しました。複数件化は段階的に(1件→数件→多件)でテストすることを学びました。
よく出るエラーと対処(実用的)
- JSON.parse エラー(Unexpected token)
- 原因:フェンスや説明が付いている。対処:LLM指示 + Codeノードでreplace(/“`json/g,”).replace(/“`/g,”)
- text.replace is not a function
- 原因:対象がオブジェクト。対処:typeofチェックや文字列化、適切なパスを指定する
- “A ‘json’ property isn’t an object”
- 原因:returnでjsonに配列を直接入れた。対処:jsonはオブジェクトで返す
元データの保持・マージで気をつけていること
生成したJSONだけ返すと、後工程でどの行の更新か対応できなくなることがありました。そこでCodeノードで元データ(例:row_numberなど)をマージして返すことにしています。Edit FieldsやMergeの設定については、他フィールドが消えないように注意して運用しています。
デバッグ手順(私のおすすめ順)
- LLMノードをまずExecuteして実際の生出力を確認する(どのパターンで来るか把握)
- Codeノードに最小の「フェンス除去→parse→return」を入れて動作確認
- 元データの結合や複数アイテム対応は、Limit=1で安定化してから拡張
- エラー時はCodeノードでrawを返し、形式をログとして残す
実際に便利になった点(私の場合)
この仕組みでルーチンの投稿下書き生成や、スプレッドシートの要約自動化を回せるようになりました。最初はエラー通知で朝の家事時間が少し乱れましたが、Codeノードで防御を固めてからは手作業が大幅に減り、記事の下書きを人が最終チェックするだけの流れに落ち着いています。
読者が実際に行動するときのポイント
- まずは1件(Limit=1)でLLM出力の生データを確認する
- Codeノードはログを残すつもりでtry/catchを書く(原因追跡が格段に楽になる)
- 複数件処理は段階的に。$input.all() と index ベースでの参照を使うと安定する
- プロンプトではStructured Output が使えるなら優先する
最後に(私なりの考え)
自動化は便利ですが、最初から完璧を求めると手が止まります。自分は「まず小さく動かす」「出力形式を観察する」「Codeノードで防御してから拡張する」という順番で安定化できました。もしこれから同じようなワークフローを作るなら、最初の数回は手動実行で出力を確かめることをおすすめします(自分の場合はこれで時間をかなり節約できました)。
この記事で使ったテンプレやノード名を埋めた形のCodeノード例や、LLMプロンプトテンプレートが必要なら個別に出します。気になる点があれば教えてください。自分もまだ改善を続けている段階なので、同じように試している人の声を聞けると嬉しいです。
コメント