Google WorkspaceでRAG実装 #ChatGPT - Qiita

1. はじめに

社内には大量のドキュメントが存在し、多くの社員が必要な情報を探すのに時間を浪費しているケースはありませんか?

こうした問題を解決するために、大規模言語モデル(LLM) を活用して社内文書に基づいたQA(質問応答)ができるボットをGoogle Chat上で提供する仕組みを構築すると、次のようなメリットがあります。

  • メリット1: 社員がGoogle Chat上で自然言語で質問するだけで、社内ドキュメントに即した回答が返ってくる
  • メリット2: ドキュメントの参照元リンクを付与して回答を補足でき、必要なら原本にすぐアクセス可能
  • メリット3: ベクトル検索により、キーワードに依存しない意味ベースのマッチングが可能
  • メリット4: 全てクラウド上で完結できるため、オンプレサーバや複雑なインフラ構築が不要

本記事では、その全体像と設定・実装方法、運用上の留意点を余すところなく解説します。
社内IT担当の方に向けて理解できる記事になっていますので、ぜひ最後までお付き合いください。


2. 課題

この記事が解決する主なポイント:

  1. Google Apps Scriptを使うことで、サーバ不要・低コストで実装できる
  2. Upstash Vector DBを用いれば手軽にベクトル検索基盤を構築可能
  3. OpenAI APIや他のLLM APIを組み合わせてRAG(Retrieval-Augmented Generation)を実現
  4. Google Chatとの連携で、社員が普段使いしているチャットツール上で完結するUXを提供

3. 全体像とアーキテクチャ

本ソリューションは大まかに以下のステップで動作します。

  1. Google Driveにある社内ドキュメントを取得
  2. OpenAI APIでテキストをEmbeddingし、Upstash Vector DBに登録
  3. Google Chatからのユーザ質問をGoogle Apps Script(Botのロジック)で受け取り
  4. 質問文をEmbedding化し、Upstash Vector DBで類似検索
  5. 検索結果をコンテキストとしてOpenAI APIに投げ、回答を生成
  6. 生成した回答をGoogle Chatに返信

このやり取りの全体フローを、以下のmermaid図で示します。

ポイント:

  • GASが主要なロジックをすべて管理し、各サービスと連携するハブとして機能
  • Upstash Vector DBが文書チャンクを意味空間で管理し、類似度検索を高速に実現
  • OpenAIなどのLLM APIでEmbedding生成と自然言語応答を行う

4. 各サービスのセットアップ

続いて、具体的な設定・構築手順を順を追って見ていきましょう。

4.1 Google Drive:ドキュメント配置・権限設定

ステップ:

  1. 参照させたいファイルをまとめるフォルダをGoogle Drive上に作成
  2. Botが利用するGoogleアカウントが閲覧権限を持つように共有設定
  3. Apps Script側でDriveAppなどの権限承認が必要なので、OAuthスコープにhttps://www.googleapis.com/auth/drive.readonlyを含める

ポイント:

  • Botは実行ユーザの権限でファイルにアクセスする形になるため、事前に共有しておく
  • PDFなどバイナリ形式の文書は、必要に応じてOCRやGoogle Docs変換でテキスト化する

4.2 Google Apps Script:プロジェクト作成とスコープ管理

  1. GASプロジェクトを作成

  2. スコープの承認

    • Driveへのアクセス権や外部API呼び出し(UrlFetchApp)のスコープを追加
  3. プロジェクト設定でSecrets管理

    • OpenAI APIキーUpstashのトークンをプロパティストアに保存し、コード上に直書きしない

実行フロー:

ポイント:

  • デプロイ時に「Google Chat アプリ」を選択し、Botとして公開できる
  • 必要なOAuthスコープをappsscript.jsonで明示し、過剰な権限を付与しないよう注意

4.3 Upstash Vector DB:インデックス作成・APIトークン取得

  1. Upstashにアカウント作成
  2. 管理画面の「Vector」タブから新しいインデックス(データベース)を作成
    • Denseを選択し、ベクトル次元数はOpenAIのembeddingモデル(text-embedding-ada-002)なら1536
    • 距離関数はコサイン類似度内積を推奨
  3. RESTエンドポイントURLBearerトークンを控える
  4. 後でApps ScriptからUrlFetchApp.fetch()で呼び出す際に使用

ポイント:

  • 1つのドキュメントを複数のチャンクに分割して保管すると検索精度が上がる
  • 名前空間(namespace)機能でデータを論理的に分割可能
  • アップサートにより既存データを更新しながら利用できる

4.4 OpenAI API:APIキー取得・モデル選択

  1. OpenAIアカウントを作成し、APIキーを発行
  2. 課金情報(クレジットカード)を登録して利用上限を設定(コスト管理のため)
  3. Embedding用エンドポイント:
    • POST /v1/embeddings
    • モデル例: text-embedding-ada-002
  4. 回答生成用エンドポイント:
    • POST /v1/chat/completions (GPT-3.5やGPT-4など)
    • max_tokenstemperature を調整

ポイント:

  • 機密情報を入力する場合は社内ポリシーを確認
  • レート制限やトークン単価に注意
  • Azure OpenAIなど代替も視野に入れる

4.5 Google Chat:Bot作成とスペースへの追加

  1. Apps Scriptの「デプロイ > 新しいデプロイ」から種類を「Google Chat アプリ」に指定
  2. Botの名前・説明・アイコンを設定
  3. 社内ドメイン限定かパブリックか選択
  4. デプロイ後、Google ChatでBot名を検索してスペースかDMに追加

イベント駆動:

  • ADDED_TO_SPACE → Botが追加されたとき
  • REMOVED_FROM_SPACE → Botが削除されたとき
  • MESSAGE → ユーザメッセージ受信時
  • CARD_CLICKED → カードボタンがクリックされたとき

onAddToSpace, onRemoveFromSpace, onMessage, onCardClick といった関数を実装することでBotが動作する。


5. Apps Scriptによるサービス連携実装

ここでは、Google Apps Scriptを使って各サービス(Drive、Upstash、OpenAI、Chat)をどう結びつけるかを解説します。大まかな流れは下図の通りです。


5.1 Google Driveからのファイル取得・テキスト抽出

実装例:

function getTextFromDrive(docId) {
  // DriveAppサービスを使用
  const file = DriveApp.getFileById(docId);
  // Googleドキュメントの場合
  const doc = DocumentApp.openById(docId);
  const text = doc.getBody().getText();
  return text;
}
  • textファイルの場合: file.getBlob().getDataAsString()
  • PDFの場合: OCRやGoogle Docs変換など追加ステップが必要

ポイント:

  • ドキュメントをチャンク(例:500~1000文字程度)に分割し、埋め込み精度を上げる
  • Driveに大きなファイルが多い場合は、定期バッチ処理などで一括抽出するとよい

5.2 Embedding生成(OpenAI)⇒ Upstashへの登録

1. Embedding生成

function generateEmbedding(textContent, openAiApiKey) {
  const url = "https://api.openai.com/v1/embeddings";
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + openAiApiKey
  };
  const payload = {
    model: "text-embedding-ada-002",
    input: textContent
  };
  const response = UrlFetchApp.fetch(url, {
    method: "post",
    headers: headers,
    payload: JSON.stringify(payload)
  });
  const result = JSON.parse(response.getContentText());
  return result.data[0].embedding; // 数値配列
}
  • text-embedding-ada-0021536次元のベクトルを返す

2. Upstashへのアップサート

function upsertToUpstash(vector, docId, chunkIndex, upstashUrl, upstashToken, originalText) {
  const payload = [
    {
      id: `${docId}-${chunkIndex}`,
      vector: vector,
      metadata: {
        filename: docId,
        chunk: chunkIndex
      },
      data: originalText // 必要ならここに抜粋文章など
    }
  ];
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + upstashToken
  };
  const response = UrlFetchApp.fetch(upstashUrl + "/upsert", {
    method: "post",
    headers: headers,
    payload: JSON.stringify(payload)
  });
  // 正常にアップサートされたかログで確認
  Logger.log(response.getContentText());
}
  • ポイント:

    • idに一意な識別子(例:docId-チャンク番号)を設定
    • metadataファイル名やページ番号など有用情報を入れておく
    • 大量のチャンクをまとめて一度にアップサートする場合は配列に複数オブジェクトを格納

5.3 質問受信(onMessage)⇒ 類似検索(Upstash)⇒ 回答生成(OpenAI)

  1. Chatからのメッセージを受け取る (onMessage)
function onMessage(e) {
  const userMessage = e.message.text; // ユーザの入力
  // 例: "ヘルプ" と打たれたらガイドを返すなど
  if (userMessage.toLowerCase() === "help") {
    return { text: "使い方ガイド: ~" };
  }
  
  // 質問に対する回答を生成
  const answer = handleUserQuery(userMessage);
  return { text: answer };
}
  1. handleUserQuery内で以下を行う:

    • 質問テキストをOpenAI APIでEmbedding化
    • Upstashに類似検索クエリを投げ、関連度の高いチャンクを取得
    • 得られたチャンクをコンテキストとしてLLMに投げ、回答生成
    • 回答テキストを返す
function handleUserQuery(queryText) {
  // 1) 質問のEmbedding
  const queryEmbedding = generateEmbedding(queryText, OPENAI_API_KEY);

  // 2) Upstashで類似検索
  const similarChunks = queryUpstash(queryEmbedding);

  // 3) LLMへのプロンプトを生成
  const contextText = similarChunks.map(chunk => chunk.data).join("n");
  const prompt = "以下は社内ドキュメントの抜粋です:n" + contextText + "nnこれに基づいて次の質問に答えてください:n" + queryText;
  
  // 4) OpenAIで回答生成
  const answer = generateAnswerWithOpenAI(prompt);
  
  return answer;
}

Upstashでの類似検索(例):

function queryUpstash(embedding) {
  const url = UPSTASH_URL + "/query";
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + UPSTASH_TOKEN
  };
  const payload = {
    vector: embedding,
    topK: 3,
    includeMetadata: true,
    includeData: true
  };
  const response = UrlFetchApp.fetch(url, {
    method: "post",
    headers: headers,
    payload: JSON.stringify(payload)
  });
  const result = JSON.parse(response.getContentText());
  return result.result; // 上位3件
}

OpenAI ChatCompletions APIで回答生成 (例):

function generateAnswerWithOpenAI(prompt) {
  const url = "https://api.openai.com/v1/chat/completions";
  const headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + OPENAI_API_KEY
  };
  const requestBody = {
    model: "gpt-4o",
    messages: [
      { role: "system", content: "あなたは社内文書に基づいて回答するアシスタントです。" },
      { role: "user", content: prompt }
    ],
    max_tokens: 1000,
    temperature: 0.2
  };
  
  const response = UrlFetchApp.fetch(url, {
    method: "post",
    headers: headers,
    payload: JSON.stringify(requestBody)
  });
  
  const json = JSON.parse(response.getContentText());
  const answerText = json.choices[0].message.content.trim();
  return answerText;
}

ポイント:

  • RAG (Retrieval-Augmented Generation) の流れをGAS内で一括制御
  • 質問Embedding → ベクトル検索 → 回答生成 というパイプライン
  • 返答が長い場合はカードメッセージや折り畳み表示を活用

6. 運用と管理のポイント

ここからは実際に運用する段階で留意すべき事項をまとめます。

6.1 データ更新・再インデックス化のワークフロー

  • Google Drive上のドキュメントが更新された場合、そのままではUpstashのベクトル情報と差が生じる
  • 定期バッチ: Apps Scriptのトリガー機能で、例えば深夜にDrive内の変更点を確認して埋め込み再生成
  • あるいは手動トリガー: 担当者が必要時にスクリプトを実行

ポイント:

  • Upsertを使えば既存のIDに対してベクトルを更新し、最新情報を常に保てる
  • 大量ドキュメントの場合はバッチ処理を分割して実行し、API呼び出し上限に注意

6.2 ベクトルDBとLLMのコスト管理

  • Upstash

    • Freeプランにクエリ数制限がある
    • 大量問い合わせやベクトル数が増えたら有料プラン検討
    • 大量アップサート時の整合性遅延に注意
  • OpenAI

    • Embedding:入力トークン数に応じた課金
    • チャット生成:入力+出力トークンで課金
    • GPT-4は単価高め、GPT-3.5は低コスト
    • レートリミット: 大規模展開時は要確認

対策例:

  • キャッシュ: 同じ質問が頻出する場合は回答をキャッシュ
  • プロンプト最適化: 不要文を削り、トークン数を減らす
  • モデル切り替え: 通常はgpt-3.5、重要質問のみgpt-4

6.3 エラーハンドリング・ログ監視

  • Apps Scriptでtry-catchしてユーザへエラー通知(「時間を置いて再度お試しください」など)
  • Logs: Logger.log()console.error()を活用し、Apps Scriptのログで状況把握
  • Google Chatの応答時間が10秒以上になるとタイムアウトの可能性があるため、効率的な呼び出しを意識

7. 精度向上と拡張のヒント

7.1 プロンプト設計・温度パラメータの調整

  • プロンプト: 「与えられた情報のみから回答し、分からなければ『分かりません』と答える」など明示的に指示
  • システムメッセージ: 「あなたは社内文書に基づいて正確に答えるアシスタントです」などでモデルのロールを設定
  • 温度: 0~0.3程度にすることで幻覚(事実無根の回答)を抑制

7.2 カードメッセージや追加機能(ヘルプコマンド等)

  • カードメッセージを使うと、回答をセクションごとに整理できる
  • 「ヘルプ」コマンド: Botに使い方を聞くと操作ガイドを返す機能
  • ドキュメント出典リンクをつける: 「参考:ファイル名やURL」


7.3 スケーラビリティとセキュリティ対策

  • スケールアップ

    • Upstashのプラン拡張、あるいは他のベクトルDBに移行も検討可
    • OpenAIの上位レートリミット申請 or 別のLLMサービスに分散
  • セキュリティ

    • Apps ScriptのOAuthスコープを最小限に
    • APIキーやトークンはプロジェクトのSecretsやVault等で安全管理
    • 社内機密情報を外部LLMに送るリスクを吟味(エンタープライズプランを使うなど)

8. まとめ

本記事では、Google Driveに格納された社内ドキュメントをUpstash Vector DBに取り込み、OpenAIのLLMを活用してGoogle Chatボットで質問応答を実現する方法を解説しました。

重要なポイントをおさらいします。

  1. GASとGoogle Chat連携: イベント駆動でユーザメッセージを受け取り、処理後に返信
  2. Upstash Vector DB: 文書チャンクをベクトル化して蓄積し、高速で意味検索ができる
  3. OpenAI API: Embeddingと回答生成に活用。モデル選択やプロンプト設計が精度を左右
  4. 運用: Driveの更新に合わせた再インデックスや、LLMのコスト管理が必須

今後の発展例:

  • Azure OpenAIへの切り替えでデータガバナンスを強化
  • Googleフォームやスプレッドシートと連携し、社内申請・ヘルプデスクなど業務フローに組み込む
  • 社内ポータルサイトや他チャットツールとも連携し、多チャネルで質問受付

本システムを導入すれば、社員が「〇〇規程はどうなってる?」「休暇手続きの方法は?」といった質問を自然言語でChatに送るだけで、即座に回答が得られるようになります。
検索効率が飛躍的に高まり、部内の問い合わせ対応負荷も軽減されることでしょう。

ぜひPoCから始めて、社内全体の情報活用レベルを一段上へ引き上げてみてください。


9. 参考文献

上記のリンク先では、Upstash Vector DBの詳細なAPI仕様やOpenAIのAPIドキュメントが掲載されており、さらに深い技術的情報を得ることができます。
もし実装でつまずいたときは、ぜひ参考にしてみてください。



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link