0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

n8n で LLM の JSON 出力を検証し、失敗したら 1 回だけ再試行する

0
Posted at

利害関係の開示:筆者は API ゲートウェイ Kunavo を運営しています。この記事は実測記録をもとに、AI の支援を使って執筆・翻訳しました。ワークフローは OpenAI 互換の Chat Completions 接続を使います。別のサービスでは Base URL、モデル名、対応パラメーターの確認が必要です。

フォームの自由記述から氏名やメールアドレスを取り出す処理を LLM に任せると、たいていはうまくいきます。ただ、ときどき次のような問題が起きます。

  • コードフェンス付きの JSON が返ってくる
  • 存在しないメールアドレスをもっともらしく作ってしまう

n8n でこれを「検証 → 1 回だけ再試行 → それでもだめなら人に聞く」という形にしたので、実測の数字と一緒に紹介します。

全体の流れ

Form Trigger → Basic LLM Chain(JSON で返させる)→ Code(検証)→ IF
   ├ OK → 完了ページ
   └ NG → 再試行チェーン(元の文・前回の出力・エラーを渡す)→ Code(検証)→ IF
              ├ OK → 完了ページ
              └ NG → Form ページ(送信者本人に足りない項目を入力してもらう)→ 完了ページ

ポイントは 3 つです。

  1. 検証はモデルに任せない:Code ノードで JSON として読めるか、必須キーがあるか、メールの形式、intent の値が列挙のどれかを確かめます。
  2. 再試行は 1 回まで:2 回目には、エラー文をそのまま渡します(例:"intent" must be one of buy, support, partnership, other.)。1 件あたりのモデル呼び出しは最大 2 回です。
  3. 検証に失敗したら人に戻す:フォームの次のページで送信者本人に不足項目を入力してもらいます。ただし、形式が正しい架空の氏名やメールアドレスは、この検証を通る可能性があります。形式検証は事実性を保証しません。実運用では原文との照合や本人確認も必要です。

検証ノード(抜粋)

const INTENTS = ['buy', 'support', 'partnership', 'other'];
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

return $input.all().map((item) => {
  const raw = String(item.json.text ?? '');
  const errors = [];
  let data = null;
  try {
    // 指示しても ``` で囲んでくるモデルがあるので剥がす
    data = JSON.parse(raw.replace(/^\s*```(?:json)?\s*/i, '').replace(/\s*```\s*$/, ''));
  } catch (e) {
    errors.push('The answer is not valid JSON.');
  }
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    errors.push('The answer must be a JSON object.');
  } else {
    if (data.email != null && !EMAIL.test(String(data.email))) errors.push(`"email" is not an email address: ${data.email}`);
    if (!INTENTS.includes(data.intent)) errors.push(`"intent" must be one of ${INTENTS.join(', ')}.`);
    if (!data.name && !data.email) errors.push('Neither a name nor an email was found.');
  }
  return { json: { valid: errors.length === 0, errors, data, raw } };
});

エラー文は英語のまま、再試行のプロンプトに入れます。モデルが直すべき箇所を名指しできるからです。

実測(n8n 2.41.4、モデル Claude Sonnet 5、2026-10-01)

ケース 結果 モデル呼び出し 1 回あたりの token(入力 / 出力)
氏名・会社・メールを含む文 全項目正しく抽出 1 208–244 / 86–95
氏名もメールもない文(「昨日の障害の件で折り返し電話がほしい」) 検証 NG → 再試行 → まだ NG → 本人入力ページ 2 再試行は 191–242 / 274–394

2 つ目のケースが大事です。モデルは再試行でも名前を作らず、検証はそれを通しませんでした。そして本人に聞く画面まで進みました。「再試行すれば必ず埋まる」と考えて作ると、ここで捏造を受け入れることになります。

つまずいた点

  • Base URL は利用するサービスの API パスまで指定する。今回の接続先では /v1 が必要でした。トップページの HTML が 200 で返るような URL は、API の正常動作を確認したことにはなりません。接続テストだけでなく、実際に 1 件実行して JSON の応答まで確認します。
  • temperature などの任意パラメーターは接続先で確認する。今回検証した Claude Sonnet 5 の接続先では、既定以外の値が 400 で拒否されました。これは今回の接続先での観測で、他社のサービス全般についての主張ではありません。
  • 人の確認ステップは誰に見せるかを決めてから作る。フォーム起点のワークフローで Wait(フォーム再開)を使うと、その次のフォームはフォームの送信者に表示されます。社内レビュー用なら Webhook 起点にして、レビュー用リンク($execution.resumeFormUrl)をレビュー担当者にだけ送ります。

ワークフロー

インポート用の JSON は認証情報を外した状態で GitHub リポジトリ から配布しています。n8n 公式テンプレートライブラリへの掲載とは別の配布です。


Kunavo で試す場合の設定:n8n 連携ドキュメント

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?