会話集/簡易RAG

簡易RAG

ゴール

簡易RAG

田中省伍@s-tanaka-4e4010
··業務効率化
👤 自分#1

はい、可能です。ただし結論を少し分けると、こうです。

「スプシ + GAS + LLM APIだけ」でも簡易RAGは作れます。
でも、検索品質をちゃんと出したいなら、埋め込みモデルAPIを使う構成が現実的です。LLM APIだけで毎回スプシ全行を読ませて「関連行を探して」とやる方式は、行数が増えると遅い・高い・不安定になりがちです。

GASは UrlFetchApp で外部HTTP/APIを呼べるので、OpenAIや他LLM/Embedding APIとの連携自体はできます。Google公式でも、Apps Scriptから外部APIへ直接リクエストできることが説明されています。(Google for Developers)
また、Apps Scriptには時間主導トリガーがあり、定期的に処理を走らせられます。これは「スプシが更新されたら裏で埋め込みを作る」運用に向いています。(Google for Developers)

おすすめ構成

一番おすすめは、スプシを簡易ベクトルDBとして使う構成です。

流れはこうです。

[スプレッドシート]
  ├─ 元データ列
  ├─ 検索対象テキスト列
  ├─ 更新日時 / ハッシュ列
  ├─ embedding列
  └─ 有効フラグ列

[GAS]
  ├─ スプシの変更差分を検知
  ├─ embedding未作成 or 変更済み行だけAPI送信
  ├─ embeddingをスプシに保存
  ├─ 検索時にクエリもembedding化
  ├─ コサイン類似度で上位N件取得
  └─ 上位N件だけLLMに渡して回答生成

[LLM API]
  └─ 検索結果をもとに回答

つまり、スプシだけを更新すれば、GAS側は自動で差分更新する形にできます。いい感じに “スプシを育てるだけ運用” に寄せられます。

OpenAIのEmbeddingは、テキストを数値ベクトルに変換して検索・クラスタリング・推薦などに使えるものとして公式に説明されています。text-embedding-3-small はデフォルト1536次元、text-embedding-3-large は3072次元です。(OpenAI 開発者)

「LLM APIだけ」でできるか?

できますが、かなり条件付きです。

たとえばスプシが100〜300行くらいで、1行も短いなら、GASで全行を読み込んでLLMに渡し、

この中から質問に関係する行を3件選んで回答してください

みたいなことはできます。

ただしこの方式は、以下がつらいです。

方式できること弱点
LLM APIだけ少量データなら簡単全件を毎回LLMに渡すので高コスト・低速
キーワード検索 + LLM実装は軽い表記ゆれに弱い。「解約」と「退会」などで漏れる
Embedding + LLMRAGらしい検索ができる初期実装が少し必要
外部ベクトルDB + LLM大量データに強い「スプシだけ更新」運用からやや遠くなる

なので、RAG検索としてちゃんと使うならEmbedding APIありがよいです。

「スプシのみ更新、GASなど周辺は全く育てない」は可能か?

ここが一番大事ですね。
結論、初期にしっかり設計すれば、かなり可能です。ただし「永久に一切メンテ不要」はさすがに危ないです。

現実的には、こういう運用ならいけます。

可能な運用

スプシに新しい行を追加する。
既存行のテキストを直す。
不要な行は有効フラグをOFFにする。
あとはGASが定期実行で差分だけembedding更新する。

この形なら、日々の運用者はスプシだけ触ればOKです。

難しい運用

列構成を頻繁に変える。
検索対象の意味が変わる。
回答ルールを頻繁に変える。
LLMモデルやAPI仕様変更に完全ノータッチで追従したい。
何万行、何十万行に増えてもスプシだけで完結したい。

このあたりは厳しいです。Apps Scriptには各種クォータや制限があり、Google公式も、クォータ超過時には例外で処理が停止すると説明しています。(Google for Developers)
つまり、データ量が増えすぎると「スプシ + GASだけ」はだんだん苦しくなります。

実装イメージ

スプシの列はこんな感じが扱いやすいです。

内容
Aid
Btitle
Cbody
Dcategory
Esource_url
Factive
Gupdated_at
Hcontent_hash
Iembedding
Jlast_embedded_at

検索対象テキストは、たとえばこう作ります。

タイトル: {title}
カテゴリ: {category}
本文: {body}

検索時は、

  1. ユーザーの質問をembedding化
  2. スプシのembedding列を読み込む
  3. コサイン類似度を計算
  4. 上位5〜10件だけLLMに渡す
  5. LLMが回答する

という流れです。

例:

質問:
「解約時の返金ルールを教えて」

検索で拾う候補:
- キャンセルポリシー
- 返金条件
- 契約終了時の対応
- 日割り返金の有無

LLMに渡す:
上位5件の本文だけ

LLMの回答:
「契約開始後の返金は原則不可ですが、未提供期間がある場合は...」

キーワード検索だと「解約」と「キャンセル」と「契約終了」が別物になりやすいですが、embedding検索なら意味が近いものを拾いやすいです。ここがRAGのうまみです。

GASでやる場合の注意点

GASは外部API連携できますが、毎回大量行を処理する設計は避けた方がいいです。Apps Scriptには実行時間や外部呼び出しなどのクォータがあるためです。(Google for Developers)

おすすめは、差分更新方式です。

毎回全行embedding作成
→ NG。遅い、高い、クォータに弱い。

変更された行だけembedding作成
→ OK。安定しやすい。

具体的には、GASで各行の検索対象テキストからハッシュを作り、前回ハッシュと違う行だけEmbedding APIに投げます。

現在の本文ハッシュ != 保存済みハッシュ
  → embeddingを再作成

現在の本文ハッシュ == 保存済みハッシュ
  → 何もしない

これなら、運用者がスプシだけ更新しても、GASは自動で追従できます。

データ量の目安

ざっくりですが、こんな判断でよいです。

データ量推奨構成
〜数百行LLM APIだけでも可。ただしEmbedding推奨
〜数千行スプシ + GAS + Embeddingで現実的
〜1万行前後工夫すれば可能。ただし遅くなりやすい
数万行以上Pinecone、Vertex AI Vector Search、Supabase pgvector等を検討
頻繁更新・多人数利用GAS単体よりバックエンド/API化推奨

「スプシだけ更新」要件が強いなら、まずは数千行までを想定したスプシ内ベクトル検索がバランス良いです。

最終回答

今回の要件なら、私はこう提案します。

最小構成:

Google Sheets
+ GAS
+ Embedding API
+ LLM API

運用:

担当者はスプシだけ更新する
GASが定期的に差分embeddingを作る
検索時はGASが類似度検索する
上位結果だけLLMに渡して回答する

避けたい構成:

検索のたびにスプシ全件をLLMに投げる

これは最初は動きますが、あとでだいたい重くなります。小さな船で大海原へ出る感じです。近海なら楽しい、外洋はしんどい、みたいな。

なので、答えは:

スプシ + GAS + LLM APIでRAG検索は可能。
ただし、実用的にやるならEmbedding APIを使うべき。
「スプシのみ更新」の運用も、差分embedding更新の仕組みにすればかなり実現可能。
ただし、データ量が大きい場合やAPI仕様変更まで完全ノーメンテにするのは難しい。

という整理になります。

1 ターン · 👁 0 · 👍 0