kintoneの仕様確認をチャット化! n8n × RAGでチーム用のAIチャットを構築してみた

こんにちは、広告事業本部でクライアントの受発注システムを担当しているリードアプリケーションエンジニアの花田です。
日々の業務の中で「あのアプリの仕様はどうなっていたか」「このJSはどう動くか」といったkintoneの仕様確認に意外と工数を取られていませんか?
今回は、iPaaSツールのn8nを用いてRAG(検索拡張生成)環境を構築し、kintoneの膨大な仕様をスムーズに確認できるAIチャットを作成しました。
実装の過程で工夫したポイントなどをまとめましたので、皆様の業務効率化のヒントになれば幸いです。

はじめに

昨今のAI技術の発展を受け実務で何か役立つものはないかと模索していました。
私たちは普段の業務でkintoneを運用していますが、そこで直面していたのが 「更新頻度の低いアプリの仕様を忘れてしまう」 という課題です。
特にkintoneの仕様管理は「kintoneJavaScriptによる独自コード」「Customineによるノーコードカスタマイズ」「kintoneプロセスの条件分岐などの設定」と多岐にわたります。
これらを確認するためには複数の場所を調べ回る必要があり手間がかかっていました。
そこで散らばった情報を一括でかつ簡単に確認できる 「kintone仕様確認用AIチャットボット」 を開発することにしました。

用語集

AI関連の用語が多く登場するので、今回のブログに登場する用語を以下にまとめました。

用語 解説
n8n 異なるサービス同士を連携させて自動化を実現するiPaaS(ワークフローツール)
※詳細はn8nの紹介記事を参照ください。
RAG Retrieval-Augmented Generationの略
AIが外部データなどから関連情報を検索(Retrieval)し、その根拠に基づいて回答を生成(Generation)する技術
ベクトルDB テキストなどのデータを「意味の近さ」で計算できる数値(ベクトル)に変換して保存する専用のデータベース
RAGの検索エンジンとして機能する
チャンク 膨大なドキュメントを、AIが処理しやすい適切な長さ(数百〜数千文字程度)に分割した「情報の断片」のこと
オーバーラップ チャンク分割する際、前後のチャンクで内容を一部重複させること
文脈が途切れるのを防ぎ、検索の精度を向上させる役割がある
エンベディング テキストをベクトルDBに保存するために、意味を持った数値データ(ベクトル)へ変換する処理のこと
ハルシネーション AIが事実に基づかない、もっともらしい「嘘」をつく現象のこと

完成! kintone仕様確認チャット

開発環境

項目 バージョン/設定
n8n 1.122.4
PostgreSQL 18.1 + PGVector拡張

チャットでできること

n8nとRAG(Vector DB)を組み合わせることで、簡単にkintoneアプリや処理の仕様を確認できるようになりました。
具体的に検索・参照可能な情報は以下の通りです。

カテゴリ 取得ソース 検索・回答できる内容
アプリ基本情報 kintone API チームが管理している全アプリの最新リスト、アプリ名の一覧取得
アクセス権限 kintone API アプリ・レコード・フィールド単位の権限、組織/ユーザーごとの閲覧・操作可否
プロセス管理 kintone API 各アプリのステータス遷移、作業者設定、承認フローの条件分岐
組織・ユーザー kintone API 組織の階層構造、各ユーザーの所属情報に基づいた権限の照合
Customine カスタマイズデータ Customineで書き出された処理による業務ロジックの検索
kintoneJavaScript 自作JavaScriptコード 各アプリに適用されている独自JSファイルの処理内容、バリデーションロジック

AIツール・モデル一覧

n8nを基盤としてGoogle Vertex AIのモデルとPostgresを活用しています。

項目 システム 役割・補足
Embeddings Google Vertex AI (gemini-embedding-001) テキストをベクトル化し、意味を抽出する役割
Chat Model Google Vertex AI (gemini-2.5-flash) ユーザーの問いに対して回答を生成するメインAI
Vector Store Postgres (PGVector) ベクトルデータの保存および高速な類似検索を実現

n8n × RAG構築のワークフロー紹介

まずは完成したワークフローを紹介します。

ワークフロー : kintone情報をベクトルDBへ集約

このワークフローでは「kintone JavaScript」「Customine」「kintone API」の3つのソースから情報を取得しベクトルDBへ保存しています。
実装におけるポイントは情報の種類ごとにテーブルを分離している点です。
現状のデータ量であれば単一テーブルでの管理も可能ですが、将来的な拡張性と情報の性質に応じた管理のしやすさを考慮、あえて分割する構成をとりました。
また検索精度を向上させるため各データには詳細なメタデータを付与しています。
これによりベクトル検索する際に「特定のアプリ情報のみ」や「特定の開発種別のみ」といったフィルタリングが可能になり、より正確な情報の引き出しを実現しています。

データ構造一覧

情報ソース 格納先テーブル メタデータ(キー:バリュー) 設計の意図・フィルタリング用途
kintone JavaScript kintone_js_vectors kintoneAppName : 各アプリ名 特定のアプリに関連するカスタマイズコードのみを絞り込むため
Customine customine_vectors kintoneAppName : 各アプリ名 アプリごとのCustomine設定ロジックを正確に参照するため
kintone API kintone_app_vectors kintoneContext : authority / kintoneList / process 「権限」「アプリ一覧」「プロセス管理」など、情報の文脈で出し分けるため

ワークフロー : ユーザーの質問から最適な回答を生成する

このワークフローではユーザーからの自然言語による質問に対し、LLM(Large Language Model)を用いて最適な回答を生成する一連の処理を行っています。
単に質問をそのままベクトルDBに投げるのではなく、「ユーザーの入力から検索対象となるメタデータを推論する」 という工程を挟んでいるのが工夫のポイントです。
これにより、例えば「〇〇アプリのJSについて教えて」という質問に対し、自動的にkintoneAppNameをフィルタリング条件として特定し、検索範囲を絞り込むことができます。
この「検索の最適化」を事前に行うことで、ノイズを減らし回答の精度を大幅に向上させています。

「kintone情報をベクトルDBへ集約」のワークフローを詳しくみてみよう!

kintoneJavaScriptをベクトルDBに保存

処理の流れとしては以下の通りです。

  1. テーブル「kintone_js_vectors」のデータを初期化
  2. Githubにあるファイルを取得
  3. 取得したファイルをLLMに渡し、JavaScriptコードの解説文章作成
  4. n8nのJavaScriptでベクトルDB用の文章作成
  5. ベクトルDBに保存(チャンク:2000、オーバーラップ:0)

ポイント : kintone JavaScriptのコードをLLMが解説してベクトルDB用の文字列を作成

当初はJavaScriptのソースコードをそのままベクトルDBに保存していました。
この状態でもある程度の回答は得られていたものの、「質問の仕方によって回答精度にバラツキが出る」 という課題に直面しました。
そこで検索精度を安定させるためにコードそのものではなく、そのコードが「何のために」「どのような処理をしているか」をLLMに要約させ人間が理解しやすい自然言語に変換します。
その情報をベクトルDBに保存することで、回答のバラツキが減りヒット率を向上させることができました。

kintoneJavaScriptを自然言語化した際のLLMプロンプト

# ロール
あなたはkintoneカスタマイズ(JavaScript)に精通したシニアエンジニアです。
提供されたソースコードを解析し、非エンジニアでも理解できる「業務上の役割」と、エンジニアが検索しやすい「技術的キーワード」を抽出して要約してください。

# 実行指示
以下の4項目を必ず含めて、日本語で出力してください。

1. **【処理の目的】**: このコードが業務上で何を実現しているか(例:重複登録の防止、ルックアップの自動更新など)。
2. **【動作タイミング】**: kintoneのどのイベント(保存時、表示時など)で動作するか。
3. **【主要なチェック・操作内容】**: どのフィールドを使い、どのようなロジックで判定しているか。
4. **【技術キーワード】**: 検索に役立つ単語(例:REST API, バリデーション, Promise.all, 重複チェック)。

# 制約事項
- 解説は正確かつ簡潔に。
- コードそのものを出力に含める必要はありません(要約のみ)。
- 日本語で出力してください。

n8n内のJavaScriptでJSONを整形処理
LLMが生成した解説文と元のソースコードを組み合わせ、ベクトルDBへの格納に最適なフォーマットへ整形します。

const nameMap = {
  "staff": "担当者申請",
  "agent": "代理申請",
  "account": "アカウント申請",
};

const dirName = $('Extract from kintoneJS File').item.json.path.split('/')[2];
const appName = nameMap[dirName] || dirName;
const fileName = $('Extract from kintoneJS File').item.json.name;
const rawCode = $('Extract from kintoneJS File').item.json.data;

let description = $json.output;

const searchFriendlyText = `
【kintoneアプリ名】: ${appName}
【ファイル名】: ${fileName}
【処理の目的】: ${description}
【プログラム概要】: kintoneアプリ「${appName}」において、${description}を行うためのJavaScriptプログラムです。
【ソースコード】:
${rawCode}
`.trim();

return {
  json: {
    kintoneAppName: appName,
    text: searchFriendlyText,
  }
};

ここでのメイン処理は、LLMが生成した「自然言語の解説文」と「生のソースコード」を単に並べるのではなく、それらをひとつの意味あるドキュメントとして再定義しています。

CustomineをベクトルDBに保存

処理の流れとしては以下の通りです。

  1. テーブル「kintone_customine_vectors」のデータを初期化
  2. GoogleドライブにあるPDFを取得
  3. PDFを1行の文字列に変換
  4. ベクトルDBに保存(チャンク:2000、オーバーラップ:0)

※ 補足:GoogleドライブのPDFは手動で置いています。今後は自動化しようと思っています。

ポイント : PDFのデータを1行の文字列に変換

Customineはノーコードで処理を構築する性質上、設定内容を書き出したPDF内には「やること」や「条件」といった日本語のロジックが豊富に含まれています。
そのためPDFからテキストを抽出して1行の文字列に整形するだけでも、ある程度は意味の通るデータとして活用可能です。
ただし現時点ではPDFのテキストを愚直に連結して1行のデータにしているため、検索精度については改善の余地があると感じています。
致命的なハルシネーションこそ発生しにくいものの、検索クエリによっては回答の内容にばらつきが生じることがあります。
本来であればkintoneJavaScriptの時のようにLLMを用いて「何を実現するための設定か」を構造化したJSONを作成するのが理想的です。
しかしCustomineにはAPIやMCPサーバーが提供されていないため、今回は「PDF出力された設定資料をベースにDB化する」という、運用でカバーできる現実的な解を選択しました。

kintone組織/グループ/アプリ名をDBに保存

処理の流れとしては以下の通りです。

  1. テーブル「kintone_app_vectors」のデータを初期化
  2. kintoneAPI / スプレットシートから取得する
  3. n8nのJavaScriptでベクトルDB用の文章作成
  4. ベクトルDBに保存(チャンク:2000、オーバーラップ:0)

※ アプリ一覧もkintoneAPIから取得できるのですが、kintoneの対象スペースに不要なアプリがあるため、スプレットシートを利用しています。

ポイント : kintoneAPIのレスポンスを自然言語へ変換

当初はkintoneAPIから取得したレコード情報や設定値のJSONをそのままベクトルDBに格納していました。
しかし構造化されただけの生のJSONデータはAIにとって文脈の理解が難しく、ハルシネーションが頻発するという課題がありました。
そこでAPIから返却されたJSONデータをそのまま入れず、プログラム側で 「人間が読む説明文」へと再構成してからベクトル化 するように変更しました。
JSON形式では単なる「キーと値」の羅列だった情報が「このグループコードは〜」といった自然言語の文章に変換されることで、ユーザーの質問(自然言語)とのベクトル的な親和性が一気に高まったためです。
この「JSONのテキスト化」というひと手間を加えた結果、ハルシネーションを抑えられることができました。

n8n内のJavaScriptでJSONを整形処理 例:kintoneグループの整形処理

let formattedText = ""; 

for (const item of $input.all()) {
  const lines = item.json.groups.map(group => {
    return `グループ「${group["name"]}」のグループコードは「${group["code"]}」です。`;
  }).join('\n');
  
  formattedText += lines + '\n';
}

return { text: formattedText.trim() };

※ 「組織」「アプリ一覧」も同じようなJavaScriptでJSONを整形しています。

kintoneプロセスと権限をDBに保存

処理の流れとしては以下の通りです。

  1. テーブル「kintone_app_vectors」のデータを初期化
  2. スプレットシートからkintoneアプリIDを取得
  3. kintoneAPIでプロセスと権限の情報を取得
  4. n8nのJavaScriptでベクトルDB用の文章作成
  5. ベクトルDBに保存(チャンク:2000、オーバーラップ:0)

ポイント : kintoneAPIのレスポンスを自然言語へ変換

こちらも他のkintoneAPI処理と同じように自然言語へ変換したJSONを作成しています。

n8n内のJavaScriptでJSONを整形処理
例:kintoneプロセスの整形処理

let combinedText = "";

for (const item of $input.all()) {
  const states = item.json.states || {};
  const actions = item.json.actions || [];
  const appName = $('Loop Over Items').item.json.kintone_name;

  // 1. 各ステータスの作業者情報を日本語化する関数
  const getAssigneeText = (stateName) => {
    const state = states[stateName];
    if (!state || !state.assignee || state.assignee.entities.length === 0) {
      return "(作業者の指定なし)";
    }

    const assigneeList = state.assignee.entities.map(ent => {
      let typeLabel = "";
      switch (ent.entity.type) {
        case "FIELD_ENTITY": typeLabel = "フィールド「" + ent.entity.code + "」で指定された人"; break;
        case "ORGANIZATION": typeLabel = "組織「" + ent.entity.code + "」"; break;
        case "GROUP": typeLabel = "グループ「" + ent.entity.code + "」"; break;
        case "USER": typeLabel = "ユーザー「" + ent.entity.code + "」"; break;
        default: typeLabel = ent.entity.code;
      }
      return typeLabel;
    }).join('、');

    const typeDesc = state.assignee.type === "ONE" ? "のいずれか1人" : "の全員";
    return `現在の作業者は ${assigneeList} ${typeDesc} です。`;
  };

  // 2. アクション情報に作業者情報を統合
  for (const action of actions) {
    const fromAssignee = getAssigneeText(action.from);
    
    let content = `kintoneアプリ「${appName}」のプロセス設定:ステータスが「${action.from}」の時(${fromAssignee})、`;
    content += `ボタン「${action.name}」をクリックすると、次はステータス「${action.to}」に遷移します。`;

    // 実行条件がある場合
    if (action.filterCond && action.filterCond !== "") {
      content += ` ただし、実行条件「${action.filterCond}」を満たす必要があります。`;
    }

    combinedText += content + "\n";
  }
}

return { text: combinedText.trim() };

kintoneプロセスには作業者やボタン表示の条件など複雑ですが、うまく文章になるようにしています。

JavaScript実行例

kintoneアプリ「担当者申請」のプロセス設定:ステータスが「承認者確認待ち」の時(現在の作業者は フィールド「manager」で指定された人 の全員 です。)、
ボタン「棄却」をクリックすると、次はステータス「棄却」に遷移します。 ただし、実行条件「status in (\"未完了\")」を満たす必要があります。

上記のような文章がベクトルDBに保存されるのですが、kintoneアプリのフィールドコードはそのまま表示されています。
理想はkintoneアプリのフィールドコードとフィールド名の対象表を用いてJavaScriptで整形することだと思います。

「ベクトルDBに保存するワークフロー」のまとめ

kintoneAPIやスプレッドシートから取得した生のJSONデータをそのままDBに投入するのではなく、一度プログラムを介して「説明文(自然言語)」へと再構成しています。
これによりユーザーの自然言語による質問とDB内のデータの意味的な距離が縮まり、検索時のヒット率を向上させることができました。
またメタデータを適切に付与することでチャットボットが検索時に条件を絞り込みよりコンテキストに沿った回答を生成できる設計にしています。
ベクトルDBに保存するときチャンク:2000、オーバーロード:0にしてます。
これはJavaScriptとCustomineは日本語の文章ではないため「中途半端に重複させても関連性を見出すのが難しいのでは?」という仮説に基づき、オーバーラップを「0」に設定してみました。
正直なところこの設定が良いのか検証中で、オーバーラップを設けた方がコード間の依存関係や前後の文脈を捉えやすくなり精度が向上する可能性も十分にあります。
このあたりのチューニングは今後の継続的な検証課題として取り組んでいく予定です。

「ユーザーの質問から最適な回答を生成する」のワークフローを詳しくみてみよう!

ユーザーの質問にメタデータを判断

処理の流れとしては以下の通りです。

  1. 上段のLLMで「権限」「プロセス」「チームが管理しているアプリ一覧」のメタデータを判断
  2. 下段のLLMで「kintoneアプリ名」を判断
  3. メタデータをJSONに整形

ポイント : ユーザーの質問に対して2つのLLMでメタデータを判断する

当初は1つのLLMに対して以下のJSON形式で返すようプロンプトを設定していました。

(例:{ kintoneContext: "authority", kintoneAppName: "発注申請" }

しかし単一のプロンプトに複数の判断を任せるとLLMが「思考プロセス」を勝手に出力してしまったり、ハッシュのキーが不足したりと後続のプログラム処理でエラーとなる不安定な挙動が散見されました。
LLMの判断ロジックを2つにすることで、LLM単体は「authority」や「発注申請」などの単語を返すだけでよくなり、余計な解説がなくシステムが安定しました。

メタデータを判断するプロンプト
例:kintoneの「権限」「プロセス」「チームが管理しているアプリ一覧」のメタデータを判断するプロンプト

# ロール
あなたは入力された文章から適切なラベルを割り当てる専門のエキスパートです。

# タスク
- ユーザーの入力に以下の【権限に関連する用語一覧】にある名称、またはそれに類するキーワードがある場合は「authority」と出力してください。
- ユーザーの入力に以下の【プロセスに関連する用語一覧】にある名称、またはそれに類するキーワードがある場合は「process」と出力してください。
- ユーザーの入力に以下の【チーム管理アプリ用語一覧】にある名称、またはそれに類するキーワードがある場合は「kintoneList」と出力してください。
- 一致するものがない場合は「なし」とだけ出力してください。
 回答は「authority」「process」「なし」のいずれかで、他の言葉は一切含めないでください。

# 【権限に関連する用語一覧】
- Div
- 組織
- 権限
- グループ
- グループコード
- ロール
- 組織コード
- AAA_BBBBのような「英語_英語」のような羅列も対象

# 【プロセスに関連する用語一覧】
- フロー
- 業務フロー
- 承認フロー
- プロセス管理
- プロセスステータス
- アクションボタン
- アクション
- 承認ボタン
- 承認ステータス

# 【チーム管理アプリ一覧】
- ◯◯◯◯管理
- ◯◯◯◯アプリ
- 管理アプリ

※ チーム名は「◯◯◯◯」にしています。

LLMがベクトルDBから検索して文章を生成

処理の流れとしては以下の通りです。

  1. ユーザーからの質問内容とメタデータのJSONをLLMが受け取ってベクトルDBに値を渡す
  2. 各ベクトルDBはJSONの値のメタデータをフィルタリングして適切なデータを返す
  3. LLMがユーザーに対して適切な文章を返す

ポイント : プロンプトにベクトルDBへの指示を明確に記載

複数接続しているベクトルDBに対しユーザーの質問内容にかかわらず必ずDBを参照するようプロンプト上で明示的な指示を加えました。
またLLMがベクトルDBを検索する際は特定の検索キー(inputのキー)を渡す必要がありますが、たまに省略することでエラーになることがあったため、検索プロセスを厳密に実行するよう定義し直しています。
さらに出力フォーマットについても「Markdownの箇条書き(- 記法)」を用いるよう具体的に指定することで、レスポンスの構造が安定し回答精度の向上を実現しました。

プロンプトの内容

# ロール
あなたはkintoneアプリの仕様に精通した専門アシスタントです。
提供されたナレッジベースの情報に基づき、誠実に回答してください。

# 実行指示(厳守事項)
1. **検索ワードの特定**: 
 - **特定したキーワードを input フィールドにセットし**、接続されているすべてのVector Storeツールを必ず実行してください。
 - 特定が難しい場合は「なし」の文字列をinputフィールドにセットしてください。

2. **全ツール強制実行**:
 - 質問の内容にかかわらず、回答を生成する前に必ず接続されているすべてのVector Storeツールを呼び出してください。

3. **データの採用基準(重要)**:
 - 「確認中」と「確認待ち」などの微細なステータスの違いは、データに記載されている通りに正確に引用してください。

4. **推測の禁止**:
 - ツールから得られた情報以外から回答を生成しないでください。

# 出力形式
必ず以下のフォーマットで出力してください。Markdownの構文(- 記法)を使い、各項目は必ず独立した行に記載してください。

【対象kintoneアプリ】
- (ここにアプリ名を記載。複数ある場合は必ず1行に1つずつ記載し、各行の先頭に「- 」を付けてください)

【詳細】
- (検索結果に基づいた仕様を記載)
- (項目が変わるごとに必ず改行し、新しい行から始めてください)
- (決して1つの段落にまとめないでください)

「ユーザーの質問から最適な回答を生成する」のまとめ

1つのプロンプトで複数の判断をさせずメタデータ抽出を2段階に分けることでノイズ(余計な思考プロセス)を排除し、システムの安定性を向上させました。
またプロンプトに「全ツール強制実行」や「推測の禁止」を明示することで、RAGの弱点であるハルシネーションを抑制できたと思います。
さらにMarkdown形式を厳格に指定することで後続処理でのエラーを防ぎ、ユーザーにとって読みやすい回答構造を維持しています。

kintoneの仕様確認をチャットの課題と検索精度

kintoneの仕様確認チャットを構築する中で、いくつかの技術的な課題も見えてきました。
メタデータによるフィルタリングを採用しているため、JavaScriptやCustomineに関する情報がkintone単体の検索にしか対応していません。
またメモリ機能未実装ゆえに「他には?」といった連続した質問ができないといった制約があります。
そのためkintoneの仕様確認をチャットは現時点でベータ版という位置づけにしています。

検索精度に関しては検証した結果ある程度利用できるようになりました。
しかしハルシネーションを完全に防ぐことは難しく、例えば「総務Div」という問いに対して「総務ユニット」などの類似した値を返してしまうケースがあります。
またプロンプトを厳格にしすぎると今度はわずかな表記揺れで「回答なし」と判定されてしまい、厳密さとのバランス調整には正解がなくRAGの奥深さを痛感しました。
今後はGoogleVertexAI gemini3系のような次世代モデルの登場により、メタデータに頼りすぎない柔軟な検索が可能になるなど、技術の進化とともに構成もさらに洗練されていくと考えています。

最後に

今回n8nとRAGの開発に初めて挑戦したことで非常に多くの学びを得ることができました。
得られた知見をチーム内外に共有し周囲にポジティブな影響を与えられたことは大きな収穫です。
開発前はRAGを「何でも解決できる魔法の杖」のように捉えていましたが、実際に手を動かすことでその得意・不得意を肌で感じることができました。
この経験を単なる技術検証で終わらせるのではなく、今後はより実務に即した形で業務フローの改善に活かしていきたいと考えています。