n8nでLLMノードを連続接続するとデータが壊れる原因とCodeノードによる解決
# n8nでLLMノードを連続接続するとデータが壊れる原因とCodeノードによる解決
n8nでOpenAIとAnthropicを連続して接続したとき、前のノードのデータが次のノードで参照できない——そんな問題で詰まった経験はありますか?
結論からお伝えします。LLMノード同士を直接繋ぐとデータが壊れます。各LLMノードの間に必ずCodeノードを挟むことで安定します。
この記事では、私が実際に構築した記事自動生成パイプラインで経験した詰まりの内容と、Codeノードを「翻訳層」として使う設計パターンを整理しています。
結論|LLMノードはそのまま繋ぐと壊れる
n8nのLLMノード(OpenAIやAnthropicなど)は、出力データを独自の構造で返します。たとえばOpenAIノードであれば、出力は以下のような形になっています。
{
"output": [
{
"content": [
{
"type": "output_text",
"text": "ここに実際のテキスト"
}
]
}
]
}この構造をそのまま次のLLMノードに渡そうとしても、$json.textや$json.outputといった単純な参照では取り出せません。LLMノードの出力は「使える形」で渡されているわけではないのです。
LLMノードを連続で接続すると、次のノードが前のデータを参照できずに壊れた状態になります。
あなたのワークフローはどこで壊れているか
詰まり方には3つのパターンがあります。どれに該当するかを確認してみてください。
パターン①:LLM出力が参照できない
症状: 次のノードで$json.textや$json.outputを参照してもundefinedになる
原因: LLMノードの出力はoutput[0].content[0].textのような深い階層に入っており、フラットなフィールドとして存在しない
確認方法: LLMノードを単体でテスト実行 → OUTPUTタブのJSONを開いて実際の階層を確認する
パターン②:上流データが消える
症状: LLMノードより前で持っていたkeywordやslugなどのデータが、LLMノードの後のノードで取れなくなる
原因: LLMノードは「自分が受け取ったプロンプトへの回答」しか返さない。上流のデータは自動的に引き継がれない
確認方法: LLMノードのOUTPUTタブを開く → keyword・slugなどのフィールドが存在しないことを確認する
パターン③:空データが渡る
症状: エラーにはならないが、次のノードのデータが空またはnullになっている
原因: Codeノードのモードが「Run Once for Each Item」になっており、並列ブランチからの複数入力が1件ずつ処理されてデータが分割されている
確認方法: CodeノードのSettingsタブ → 「Run Once for All Items」になっているか確認する
なぜLLMノードはそのまま繋いではいけないのか(設計視点)
「接続は正しいはずなのに、なぜデータが来ないのか?」——この問いの答えはツールの問題ではなく、構造の問題にあります。
LLMノードは「与えられたプロンプトに答える」という1つの仕事しかしません。前のノードから来たデータを「覚えておく」機能はなく、自分の回答だけを出力します。これはn8nのバグではなく、LLMノードの仕様です。
ここで重要なのが「動く」と「正しく動く」の違いです。
- 動く:ワークフローがエラーなく実行される
- 正しく動く:全ノードで意図したデータが流れている
私が最初に詰まったのも、ワークフロー自体はエラーなく「動いていた」のに、データが正しく渡っていない状態でした。エラーがないからこそ原因特定に時間がかかりました。
この問題を解決するのがCodeノードです。LLMノードの後にCodeノードを挟み、「何を次に渡すか」を明示的に設計することで初めて「正しく動く」状態になります。
Codeノードが「翻訳層」になる理由
Codeノードは、前のノードの出力から必要なフィールドだけを取り出し、次のノードが扱いやすい形に整形して渡す役割を担います。私はこれを「翻訳層」と呼んでいます。
NG例:LLMノードを直接繋いだ場合
次のOpenAIノードのプロンプトで{{ $json.keyword }}と書いても、LLMノードの出力にkeywordフィールドは存在しないためundefinedになります。
OpenAI(市場調査)→ OpenAI(記事設計)
↑ $json.keyword が取れないOK例:Codeノードを挟んだ場合
OpenAI(市場調査)→ Code(翻訳)→ OpenAI(記事設計)
↑ $json.keyword が取れるケース別コード
ケース1:LLMのテキスト出力を取り出して上流データと合わせる
// LLMノードの出力からテキストを取り出す
const text = $input.first().json.output[0].content[0].text;
// 上流のCodeノードから引き継いだデータを取得する
// ※ $('ノード名') の部分は自分のワークフローの実際のノード名に変更する
const upstream = $('データ統合Code').first().json;
return [{
json: {
keyword: upstream.keyword,
intent: upstream.intent,
slug: upstream.slug,
llm_result: text
}
}];ケース2:LLMがJSON形式で出力してきた場合(パースが必要)
const rawText = $input.first().json.output[0].content[0].text;
// ```json ... ``` のマークダウン記法を除去してからパース
const cleaned = rawText.replace(/```json\n?/g, '').replace(/```/g, '').trim();
const parsed = JSON.parse(cleaned);
const upstream = $('データ統合Code').first().json;
return [{
json: {
keyword: upstream.keyword,
intent: upstream.intent,
market_research: parsed
}
}];汎用的な書き方のポイント:
$('ノード名')の部分は自分のワークフローのノード名に合わせて変更する- 上流から引き継ぎたいフィールドを明示的に列挙する
- LLMの出力は必ず
output[0].content[0].textから取り出す(Simplify Output: ONの場合)
ただし、このままだと自分のプロジェクトのどのノードで同じ問題が起きるかは判断できません。
なぜなら、「どの情報をどのノードに渡すか」を設計する判断基準がまだ整理されていないからです。
→ 判断軸の整理はこちら
実際の解決手順
では私が実際にどのような手順で解決したかをお伝えします。
1. LLMノードの出力構造を確認する
LLMノードを単体でテスト実行して、OUTPUTのJSONタブで実際の構造を確認します。output[0].content[0].textのような深い階層になっていることがほとんどです。
2. Codeノードを追加してテキストを取り出す
LLMノードの直後にCodeノードを追加します。設定は「Run Once for All Items」にします。
const text = $input.first().json.output[0].content[0].text;3. 上流のデータを明示的に引き継ぐ
上流ノードのデータが必要な場合は$('ノード名')で参照します。
const upstream = $('データ統合Code').first().json;
return [{
json: {
keyword: upstream.keyword,
intent: upstream.intent,
market_research: JSON.parse(text)
}
}];4. 次のLLMノードのプロンプトで参照する
Codeノードが整形したデータは{{ $json.keyword }}のような形で参照できます。
判断基準:
- LLMノードの出力を次のノードで使いたい → Codeノードを挟む
- 上流のデータを引き継ぎたい →
$('ノード名').first().jsonで明示的に参照する - Codeノードで複数回実行されてしまう → 「Run Once for All Items」に変更する
うまくいかないときの確認手順
設定が正しいはずなのに動かない場合は、以下の順番で確認してみてください。
OUTPUTタブで構造を確認する
各ノードを選択して「Execute step」でテスト実行した後、右側のOUTPUTタブを開きます。「JSON」タブをクリックすると実際のデータ構造が見えます。ここでkeywordやintentが存在するか確認してください。
console.logでデバッグする
Codeノードの先頭にconsole.logを追加すると、ブラウザのF12コンソールに出力されます。
const allItems = $input.all();
console.log('アイテム数:', allItems.length);
console.log('最初のアイテム:', JSON.stringify(allItems[0]?.json));これでアイテムが何件来ているか・どんな構造かを確認できます。
JSON構造の見方
OUTPUTタブで見たJSONの読み方は以下の通りです。
{
"output": [ ← ここが配列
{
"content": [ ← ここも配列
{
"text": "..." ← 実際のテキストはここ
}
]
}
]
}output[0].content[0].textという参照は「outputの0番目の要素のcontentの0番目の要素のtext」という意味です。
安定する設計パターンと判断基準
「ではどんな設計にすれば安定するのか?」——この経験から、LLMを複数使うパイプラインには「先に設計を決める」ことが重要だとわかりました。
安定する設計パターン:
LLMノード①
→ Codeノード(翻訳・整形)
→ LLMノード②
→ Codeノード(翻訳・整形)
→ LLMノード③LLMノードの後には必ずCodeノードを挟む。これがルールです。
設計前に確認すべき判断基準:
- このLLMノードの出力から何を取り出すか → 先に決める
- 次のノードに何を渡すか → 先に決める
- 上流のどのデータを引き継ぐか → 先に決める
「ノードを繋ぎながら考える」ではなく、「データの流れを設計してからノードを並べる」という順番に変えるだけで、詰まる箇所が大幅に減ります。
この後どうなったか・よくある質問
この設計パターンを定着させたことで、市場調査→コンテキスト読み込み→記事設計→インタビュー生成→Slack送信という4段階のLLMパイプラインが安定して動くようになりました。Codeノードを「データの橋渡し役」として明確に位置づけることで、ノード間のデータフローが追いやすくなったと感じています。
Q. Codeノードは必ずRun Once for All Itemsにしないといけませんか?
前後のアイテム数によります。並列ブランチをMergeで統合した後はRun Once for All Itemsにする必要があります。単一のLLMノードの後であればRun Once for Each Itemでも動く場合があります。ただし複数入力が来る可能性があるなら最初からRun Once for All Itemsにしておく方が安全です。
Q. Simplify OutputのON/OFFでデータ構造は変わりますか?
変わります。ONの場合はoutput[0].content[0].text、OFFの場合はさらに深い構造になることがあります。まずONのままでテストして出力構造を確認してからCodeノードを書くのが効率的です。
まとめ
n8nでLLMノードを連続接続するときのポイントをまとめます。
- LLMノードの出力は複雑な階層構造で返ってくる
- そのまま次のノードに渡してもデータは参照できない
- 各LLMノードの後にCodeノードを挟んで翻訳・整形する
- 上流のデータは
$('ノード名')で明示的に引き継ぐ - 「Run Once for All Items」の設定を忘れない
今日から試せる一歩として、まず既存のLLMノードの出力構造をOUTPUTタブで確認してみてください。$json.textで取れると思っていたデータがどの階層にあるかを確認するだけで、Codeノードで何をすればいいかが見えてきます。
ただし、このままだと「何をどの順番で設計するか」という判断は難しいままです。
なぜなら、個別のノード設計より先に、ワークフロー全体の設計判断基準が必要だからです。
→ 判断軸の整理はこちら
bizinets.biz
記事を読んで「もっと知りたい」と思ったあなたへ
bizinets.biz では、ビジネスの仕組みづくりを体系的に学べるコンテンツを用意しています。まずは一度、覗いてみてください。
bizinets.biz へ行く →