bbizinets.life
n8nで重複チェックが機能しない原因と修正方法|LLM前に置くべき理由
実践ログ

n8nで重複チェックが機能しない原因と修正方法|LLM前に置くべき理由

n8nでAI記事生成パイプラインやキーワード管理フローを作っている場合、keyword_queue.csvとkeyword_done.csvを照合して、記事化済みキーワードを止める設計が必要になります。

n8nでキーワードの重複チェックを実装していたのに、記事化済みのキーワードでLLMが動いてしまいました。keyword_done.csvとの照合ロジックは正しく実装できていました。問題はチェックの位置でした。LLMを数回呼び出した後に「このキーワードはdone済みです」という通知が来る設計になっていたのです。

この記事でわかること:

  • n8nで重複チェックが機能しない本当の原因
  • keyword_done.csvとの照合をLLM前に置く実装方法
  • Codeノード・IFノードの具体的な設定
  • ノード名固定が重要な理由と実装時の注意点

結論:重複チェックはLLMを呼ぶ前に置く

n8nで重複チェックを入れる場合、重要なのは「チェック処理を作ること」ではなく「LLMを呼ぶ前に置くこと」です。

keyword_done.csvとの照合ロジックをどれだけ正確に実装しても、LLMノードより後ろに配置していると、クレジットを消費してからはじめて「重複していました」と判明します。これは防止ではなく事後報告です。

修正後の正しいフロー位置はこうです。

keyword_queueダウンロード
→ キーワード書き換え
→ キーワード変換
→ keyword_done取得 ← ここに移動
→ keyword_doneExtract
→ 重複チェック(Codeノード)← LLMより前
→ 重複あり?(IFノード)
  ├─ true → Slack通知 → 停止
  └─ false → LLM群へ続行

発生した問題:記事化済みキーワードでSlackインタビューが届いた

n8nでAI記事生成パイプラインを運用していたとき、Slackにインタビュー質問が届きました。最初は「フローが正常に動いている」と思ったのですが、質問内容を見ているうちに見覚えがある気がしました。

以前にも同じテーマのインタビューに回答した記憶があり、遡って確認するとやはり重複していました。keyword_queue.csvとkeyword_done.csvを並べると、同じキーワードが両方に入っていました。

フロー上でどの段階まで処理が走っていたかというと、こうです。

keyword_queueダウンロード
→ キーワード変換
→ LLM(市場調査)← APIクレジット消費
→ LLM(記事設計)← APIクレジット消費
→ LLM(インタビュー生成)← APIクレジット消費
→ Slack送信
→ 重複チェック通知「このキーワードはdone済みです」

重複チェックの通知はSlack送信の後に来ていました。つまり、LLMを3回呼び出した後にはじめて「実はdone済みでした」と分かる設計でした。

背景には、keyword_queue.csvが上書き消去されるトラブルがありました。n8nの実行履歴から古いデータを復元したのですが、その復元データに記事化済みキーワードが混入していました。重複チェックがLLM後に動く設計だったため、そのまますり抜けてしまったのです。

n8n×Claude×WordPressの記事自動投稿パイプライン全体設計でも書いていますが、このパイプラインはフロー1・2・フロー0の3フロー構成になっています。今回の問題はフロー1の前半部分の設計でした。

原因:keyword_done.csvは取得しているのにLLM後にしか判定されなかった

なぜこうなっていたのかというと、当初の設計では keyword_done.csv の取得が「並列ファイル取得」ブランチの1本として組み込まれていたからです。

キーワード変換
  ├─ Get internal-links → Extract
  ├─ Get title-rules → Extract
  ├─ Get article-json → Extract
  └─ Get keyword-done → keyword_doneExtract
       ↓
    Merge(Appendモード)
       ↓
    統合後の整形
       ↓
    summary_cache取得
       ↓
    関連スコアリング(← ここで重複判定していたがLLM後だった)

keyword_done.csvは確かに取得していました。しかしその照合処理が、LLMを呼び出した後の「関連スコアリング」ノードの中で行われていました。関連スコアリングは、記事に使う内部リンク候補を選ぶための処理です。私のフローでは、この後段処理の中に重複判定が残っていました。つまり問題は、チェック処理そのものではなく、チェックをどの段階に置くかという設計でした。

なぜLLM後のチェックでは意味がないのか

LLMのAPIコールはクレジットを消費します。市場調査・記事設計・インタビュー生成の3ノードが動くと、合計でかなりのクレジットが使われます。その後に「重複していました」と通知が来ても、消費したクレジットは戻りません。

さらに、インタビューがSlackに届いた時点で人間の確認コストも発生します。「あれ、これ前にも来たな」と気づいて遡って確認する作業は、自動化の恩恵を打ち消します。

チェックは「何かを防ぐ」ために置くものです。防ぐには、防ぎたい処理より前に置く必要があります。重複キーワードを防ぎたいなら、LLMより前に置くのが正解です。

修正手順:重複チェックをキーワード変換直後に移動する

手順1:keyword_done取得ノードを並列ブランチから切り離す

既存の並列ファイル取得ブランチから Get keyword-done ノードの接続を切ります。ノード自体は削除しても構いませんが、新しいノードとして追加し直した方がノード名管理がしやすいです。

手順2:キーワード変換の直後にkeyword_done取得→Extract→重複チェックの流れを追加

ノード名(固定・変更禁止):

役割 | ノード名
--- | ---
keyword_done取得 | `keyword_done取得`
Extractノード | `keyword_doneExtract`
重複チェックCode | `重複チェック`
重複判定IF | `重複あり?`
重複時Slack通知 | `重複キーワード通知`

手順3:重複チェックCodeノードの内容

※以下のコードは、keyword_done.csvの1列目にキーワードが入り、各行を | 区切りで管理している前提です。カンマ区切りCSVで管理している場合は、split(' | ') の部分を自分の形式に合わせて変更してください。

// キーワード変換ノードから現在のkeywordを取得
const keyword = $('キーワード変換').first().json.keyword.trim();

// keyword_doneの内容をパース
const doneText = $('keyword_doneExtract').first().json.data;

const doneLines = doneText.split('\n').filter(l => l.trim() && !l.startsWith('keyword'));
const doneKeywords = doneLines.map(l => l.split(' | ')[0].trim());

const isDuplicate = doneKeywords.includes(keyword);

return [{ json: { keyword, isDuplicate } }];

手順4:IFノード「重複あり?」の設定

  • 条件:{{ $json.isDuplicate }} is equal to true
  • boolean型と文字列の判定ズレを避けるため、必要に応じて「Convert types where required」をONにします
  • true → 重複キーワード通知(Slackノード)→ フロー終了
  • false → 既存の並列ファイル取得ブランチへ続行

手順5:重複キーワード通知のSlackメッセージ

⚠️ キーワード重複を検出しました
keyword: {{ $('重複チェック').first().json.keyword }}
状態: keyword_done.csvに記録済みです
対応: keyword_queue.csvからこのキーワードを手動で削除してください

手順6:統合後の整形ノードのコードを確認

keyword_doneExtractのノード名が変わっている場合は、統合後の整形 ノード内のコードも合わせて修正します。

// この行のノード名が固定名と一致しているか確認する
const keywordDoneText = $('keyword_doneExtract').first()?.json.data ?? null;

ノード名を固定しないとコード参照が壊れる

今回の修正でもう一つ重要な点がありました。$('ノード名').first() でノードを直接参照しているコードは、ノード名を変更すると参照が壊れます。

実際に今回、ノード名の表記揺れ(ハイフンとアンダースコアの違い)で Referenced node doesn't exist エラーが発生しました。

対策:ノード名を事前に固定して変更禁止にする

新しいノードを追加したり、既存ノードの役割を変えたりするとき、ノード名を変えたくなることがあります。しかし $('ノード名').first() 参照を使っているコードがある場合、名前を変えると即座にエラーになります。

ノード名のルール:

  • 決めたら変更しない
  • 日本語でも英語でも構わないが、表記を統一する(ハイフンかアンダースコアか)
  • コードで参照している全ノード名をドキュメントに残しておく

n8nのMergeノードでデータが取れない原因と直接参照で解決した実践ログでも書いていますが、$('ノード名').first() 参照はノード名依存のため、命名ルールを決めておくことが安定運用の前提になります。

修正後どうなったか

重複チェックをLLM前に移動してから、テストとして keyword_done.csv に記録済みのキーワードをkeyword_queue.csvに入れてフローを実行しました。LLMを一切呼び出さずに重複検出のSlack通知が届き、フローが停止しました。

クレジットを消費する前に止められるようになったことで、フローへの信頼感が戻りました。また今回の対応と合わせて、session.jsonの残存チェックのパス問題も修正できました。詳細はn8nでWebhook後のデータ消失をsession.jsonで解決した実践ログに記録しています。

まとめ

n8nで重複チェックが機能しない原因は、チェックロジックの問題ではなく「チェックの位置がLLMより後ろにある」という設計の問題でした。

対処のポイントは3点です。

  • keyword_done取得とExtract・重複チェックCodeをキーワード変換の直後に配置する
  • IFノードで必要に応じて「Convert types where required」をONにする
  • ノード名を固定して変更禁止にし、コード参照が壊れないようにする

今回の問題は実装上の詰まりでもありますが、本質は「チェックをどこに置くか」という設計判断の問題でした。

n8nの設定だけを直しても、別のフローで同じようにチェック位置を間違える可能性があります。自動化フロー全体で、どこに判断ポイントを置くべきかを整理したい方は、bizinets.bizのチェックは置く場所で意味が変わるが参考になると思います。

bizinets.biz

記事を読んで「もっと知りたい」と思ったあなたへ

bizinets.biz では、ビジネスの仕組みづくりを体系的に学べるコンテンツを用意しています。まずは一度、覗いてみてください。

bizinets.biz へ行く →

関連記事