スプレッドシートのデータをDifyナレッジベースAPIで一括チャンク化してみた

AIエンジニア

1. はじめに

1-1. Difyとは?

Difyは、テキスト分割(チャンク化)や埋め込みモデルによる検索を行い、LLMを活用したアプリケーションを効率的に作れるプラットフォームです。ドキュメントをナレッジベースとして登録することで、類似検索チャットボットへの回答源として利用できます。

1-2. 本記事の目的と想定読者

本記事では、スプレッドシート(Google Sheets)に登録された求人データを、GAS(Google Apps Script)とDifyのAPIを使って一括アップロードし、1レコード=1チャンクとして扱う実装例を紹介します。
想定読者は、GASやAPIを用いた自動化に慣れているエンジニア、またはシステム担当者です。


2. スプレッドシートからDifyナレッジベースへの自動同期

2-1. なぜ「1レコード=1チャンク」がおすすめなのか?

求人情報は、一つひとつが独立した情報として管理されるケースが多いです。「1レコード=1チャンク」にしておくと、検索やLLM回答時に、特定求人のみが抽出されるため誤混入リスクが低減し、ユーザーが絞り込みやすくなります。

2-2. 全求人を1つのファイルにまとめるアプローチ

Difyでは「ファイルごと」に分割ルール(process_rule)が適用される仕組みです。そのため、全レコードを単一ファイル(all-jobs.txt)にまとめてアップロードし、「\n\n\n」などの区切り文字でDify側が分割してくれるように設定するのがシンプルです。


3. 具体的な実装ステップ

3-1. ナレッジベース(Dataset)の作成

  1. UIからの新規作成
    Difyのダッシュボードに入り、「ナレッジベース」を作成。「test」「MyJobsDataset」など任意の名前を指定します。
  2. APIからの新規作成
    GASからPOST https://api.dify.ai/v1/datasetsを呼ぶことで作成可能。返却されるdataset_idを後続処理で利用します。
// 例: createDataset関数
function createDataset(name, permission) { ... }

3-2. スプレッドシートの構造を整備

以下のように「会社名」「ポジション」「業務内容」「応募条件」「給与」「都道府県」等の列を用意し、1行1求人で管理します。

会社名ポジション業務内容応募条件給与都道府県
株式会社アクメソフトウェアエンジニアシステム機能の設計・実装, CI/CD保守, …3年以上のJavaまたはPython経験月給40万円東京都

3-3. GASでのスクリプト例

以下は「すべての求人を1つのファイル(all-jobs.txt)にまとめる」サンプルコードです。各求人を\n\n\nで区切りし、Difyへcreate-by-textAPIを呼び出してアップロードします。

/**
 * メイン関数:スプレッドシートの求人データを1つの大きなファイル (all-jobs.txt) としてDifyにアップロード。
 * 各行は "\n\n\n" 区切りとなり、Dify 側では行ごとにチャンク化される想定。
 */
function syncJobsAsSingleFile() {
  Logger.log("=== 開始: syncJobsAsSingleFile ===");
  
  var datasetId = "YOUR_DATASET_ID";  // or createDataset("MyJobsDataset", "only_me");
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Jobs");
  if (!sheet) throw new Error("シート 'Jobs' が見つかりません。");
  
  var values = sheet.getDataRange().getValues();
  if (values.length < 2) return;

  var headers = values[0];
  var dataRows = values.slice(1);

  // カラムインデックスを探す
  var companyCol      = headers.indexOf("会社名");
  var positionCol     = headers.indexOf("ポジション");
  var dutiesCol       = headers.indexOf("業務内容");
  var requirementsCol = headers.indexOf("応募条件");
  var salaryCol       = headers.indexOf("給与");
  var prefCol         = headers.indexOf("都道府県");

  // 行ごとに文字列を構築
  var lines = [];
  for (var i = 0; i < dataRows.length; i++) {
    var row = dataRows[i];
    var lineText = "";
    lineText += "会社名: "     + (row[companyCol]      || "") + "\n";
    lineText += "ポジション: " + (row[positionCol]     || "") + "\n";
    if (dutiesCol >= 0)       lineText += "業務内容: "    + (row[dutiesCol]       || "") + "\n";
    if (requirementsCol >= 0) lineText += "応募条件: "    + (row[requirementsCol] || "") + "\n";
    if (salaryCol >= 0)       lineText += "給与: "        + (row[salaryCol]       || "") + "\n";
    if (prefCol >= 0)         lineText += "都道府県: "    + (row[prefCol]         || "") + "\n";

    lines.push(lineText);
  }
  
  // "\n\n\n" で連結
  var docText = lines.join("\n\n\n");
  
  // 1ファイルとしてDifyに登録
  var docName = "all-jobs.txt";
  var documentId = createSingleFileWithOverlap(datasetId, docName, docText);
  Logger.log("アップロード完了 => docId=" + documentId);
}

/**
 * Difyに 1ファイルを create-by-text でアップロードし、separator="\n\n\n" / overlap=250 などを設定
 */
function createSingleFileWithOverlap(datasetId, docName, docText) {
  var apiKey = "Bearer YOUR_DATASET_API_KEY"; 
  var url = "https://api.dify.ai/v1/datasets/" + datasetId + "/document/create-by-text";

  var payload = {
    name: docName,
    text: docText,
    indexing_technique: "high_quality",
    process_rule: {
      mode: "custom",
      rules: {
        pre_processing_rules: [
          { id: "remove_extra_spaces", enabled: true },
          { id: "remove_urls_emails",  enabled: false }
        ],
        segmentation: {
          separator: "\n\n\n",
          max_tokens: 2000,
          overlap: 250
        }
      }
    }
  };

  var options = {
    method: "post",
    headers: {
      "Authorization": apiKey,
      "Content-Type": "application/json"
    },
    muteHttpExceptions: true,
    payload: JSON.stringify(payload)
  };

  var response = UrlFetchApp.fetch(url, options);
  var status   = response.getResponseCode();
  var text     = response.getContentText();
  if (status !== 200) {
    throw new Error("createSingleFileWithOverlap 失敗: status=" + status + " body=" + text);
  }

  var json = JSON.parse(text);
  return json.document.id;
}

4. Dify側でのチャンク設定

4-1. process_rule の重要パラメータ

  • separator: テキスト内を分割する区切り文字列。\n\n\n などを指定すると「三重改行単位」でチャンク化されます。
  • overlap: 分割したチャンク同士をどれだけ重複させるか。例:250トークン分を重複させることで、文脈が途切れにくくなります。
  • max_tokens: 1チャンクの最大トークン数。ここを小さくすると、長いテキストがさらに分割される可能性があるので注意。

4-2. 改行コードや分割ルールでハマりやすいポイント

  • Windows系の改行が \r\n の場合、単に "\n\n\n" 指定してもヒットしないケースあり。
  • GASで docText.replace(/\r\n/g, "\n") などして、すべて \n に統一しておくと安全。

5. トラブルシュート

5-1. 区切り文字が反映されないとき

  • Payloadのseparatorが正しく"\n\n\n"になっているか確認します。
  • ログにdocTextを表示し、実際の文字列内に "\n\n\n" が入っているかを確認しましょう。

5-2. Overlap・max_tokens設定の注意点

  • Overlapを大きくしすぎると、実質的にチャンクが重複し、検索に不要なノイズが混入する可能性もあります。
  • max_tokens をあまり小さくすると、本来1求人が1チャンクになるはずが追加で分割される原因となります。

5-3. APIキーエラーで401/403が出る場合

  • Bearer <API_KEY>の形式が正しいか(”Bearer “の半角スペースを含むか)。
  • Dataset用のAPIキーとApp用のAPIキーを間違えていないか。
  • Dify管理画面でAPIキーが**「revoked」(破棄)**されていないか再チェック。

6. まとめと今後の拡張

6-1. メタデータ(都道府県など)の活用

  • Difyではメタデータを追加して、例えば「都道府県=○○」でフィルタリングする検索なども可能です。
  • metadata API や GASからの一括割当などを利用し、より柔軟なクエリを実現できます。

6-2. APIリクエスト数の管理

  • 大量レコードを細切れで登録する場合、Rate Limit(レート制限)や総リクエスト数に注意してください。
  • 今回の「全レコードを1ファイル」とする方針なら、アップロード回数は1回のみで済むので大幅にAPIコール数を削減できます。

6-3. 今後の拡張: 自動タグ付けや外部サービス連携

  • ChatGPTプラグイン、Airflowや他のETLツールとの連携、Notion等からの転送など、拡張方法は多彩です。
  • 企業独自の業務フローに合わせて、GAS×Dify×外部APIでワンストップなデータパイプラインを組むのも面白いでしょう。

まとめ

本記事では「スプレッドシート上にある求人データを、Difyで1ファイルとしてアップロードし、\n\n\n 区切りでチャンク管理する」という実装手順を紹介しました。GASスクリプト例DifyのAPIを組み合わせることで、一括登録・チャンク管理・検索最適化をスムーズに自動化できます。

ぜひ今回のコード例を参考にして、Difyとスプレッドシートの連携を試してみてください。

タイトルとURLをコピーしました