学習記録 #5 後編】GAS × Gemini で RAG チャットボットを自作した
요약
본 기술 기사는 Google Apps Script(GAS)와 Gemini API를 활용하여 RAG(Retrieval-Augmented Generation) 챗봇을 직접 구축하는 과정을 심층적으로 다루고 있습니다. 특히, 임베딩 과정에서 L2 정규화의 중요성, `taskType`에 따른 벡터 최적화 사용법 등 기술적인 핵심 원리를 상세히 설명합니다. 또한, Google Sheets를 벡터 DB로 활용하고 쿼리 차이 감지를 위한 '차분 빌드' 기법을 적용하는 실질적인 구현 방법과, 실제 서비스 배포 시 발생할 수 있는 API 할당량(Quota) 문제 해결책까지 제시하여, 독자가 높은 수준의 이해도를 바탕으로 프로젝트를 완성할 수 있도록 안내합니다.
핵심 포인트
- RAG 파이프라인 구축 과정: 임베딩 → 검색 → LLM 호출의 각 단계별 구현 상세 내용을 다룹니다.
- 임베딩 벡터 처리 핵심 기술: Gemini Embedding API 사용 시 L2 정규화가 필수적이며, 이를 통해 코사인 유사도 계산을 단순 내적으로 대체할 수 있습니다.
- 검색 정확도 향상 기법: `taskType` (RETRIEVAL_DOCUMENT vs RETRIEVAL_QUERY)을 구분하여 사용하는 것이 검색 성능에 큰 영향을 미칩니다.
- 실용적인 데이터 관리 전략: Google Sheets를 벡터 DB로 사용하며, MD5 해시 기반의 '차분 빌드' 기능을 구현하여 API 호출 비용과 시간을 절약합니다.
- 배포 및 운영 팁: Gemini Pro Preview 모델은 무료 할당량이 적으므로, 검증 단계에서는 Gemini 2.5 Flash와 같은 안정적인 모델을 사용하는 것이 권장됩니다.
[前編] では、GAS で自作する理由、AI のデータ処理の仕組み、環境構築のつまずきポイントを記録しました。
後編では、実装の詳細、デプロイ、テスト結果、そして精度改善計画を記録します。
対象読者:エンジニア経験 1 年程度の方。前編を読んで環境構築まで完了している前提です。
この記事で分かる 3 点を最初に示します。
- RAG パイプラインの実装詳細― Embedding、検索、LLM 呼び出しの各工程 -
- デプロイとテスト― Web App として公開し、動作確認するまで -
- 精度改善計画― 回答精度を上げるための具体的な改善ロードマップ
Claude Code で一気にコード生成しましたが、生成されたコードの中で特に重要なポイントを解説します。
テキストを数値配列に変換する処理です。RAG の精度を左右する最重要コンポーネント。
使用モデル: gemini-embedding-001
テキスト:「腹水の主症は腹部膨大である」
│
│ Gemini Embedding API
...
重要ポイント:L2 正規化
Gemini Embedding 2 は MRL(Matryoshka Representation Learning)により、3072 次元→768 次元にスケールダウンできます。API リクエスト時に outputDimensionality: 768 を明示指定することで次元数を削減できます。ただし次元数を変えるとベクトルの長さ(ノルム)が変わるため、L2 正規化(ベクトルの長さを 1 に揃える処理)が必須です。
【L2 正規化のイメージ】
正規化前:
ベクトル A: 長さ 2.5 → [0.5, 1.0, 2.0, ...]
...
L2 正規化を行うことで、コサイン類似度の計算が単純な内積で済むようになります。
taskType の使い分け:
| 場面 | taskType | 理由 |
|---|---|---|
| 資料をベクトル化するとき | RETRIEVAL_DOCUMENT | ドキュメント用の最適化がかかる |
| 質問をベクトル化するとき | RETRIEVAL_QUERY | クエリ用の最適化がかかる |
同じテキストでも taskType が異なるとベクトルが変わります。ドキュメントとクエリで別の最適化がかかるため、正しく使い分けることで検索精度が向上します。
実装コード(核心部分):
// TODO: 実際のコードに差し替え(embedDocument / embedQuery の核心部分)
function embedText(text: string, taskType: string): number[] {
const response = UrlFetchApp.fetch(
...
本格的なベクトル DB(Pinecone、Chroma など)の代わりに、Google Sheets をベクトルデータベースとして使っています。
Sheets の構成:
| 列 | 内容 | 例 |
|---|---|---|
| A | id | doc_001_chunk_0 |
| B | type | subject or topic |
| C | text | チャンクのテキスト |
| D | metadata | {"fileName":"鼓脹.pdf","chunkIndex":0} |
| E | embedding | [0.12,-0.34,0.56,...] |
| F | textHash | a1b2c3d4...(差分ビルド用) |
差分ビルド: テキストの MD5 ハッシュを保存しておき、次回実行時にハッシュが変わっていないチャンクはスキップ。大量資料でも Embedding API の呼び出し回数を最小限に抑えられます。
【差分ビルドの流れ】
初回:全チャンク → ベクトル化 → Sheets 保存(100 チャンク)
処理時間:約 5 分
...
質問ベクトルと各チャンクベクトルの「意味の近さ」を計算して、最も関連性の高いチャンクを取得します。
質問:「腹水の主症は?」
│
│ ベクトル化
...
実装コード(核心部分):
// TODO: 実際のコードに差し替え(cosineSimilarity の核心部分)
function cosineSimilarity(vecA: number[], vecB: number[]): number {
// L2 正規化済みベクトルなら内積 = コサイン類似度
...
ハイブリッド検索: 2 種類のチャンクを別レーンで検索してマージする設計にしています。
【subject チャンク】科目・テーマごとのまとまり
→ 「腹水について」の情報を広く取得
【topic チャンク】トピックごとのまとまり
...
検索結果(関連チャンク)と質問を組み合わせて、LLM に回答を生成させます。
┌─────────────────────────────────────┐
│ LLM に渡すプロンプト │
│ │
...
Gemini Pro Preview 系を使って 429 エラーが出た話:
実際に GAS 版でも Gemini Pro Preview 系モデルで 429 エラーが発生しました(gemini-2.5-pro 系は無料枠が非常に少ない)。
429 RESOURCE_EXHAUSTED
Quota exceeded for metric: generate_content_free_tier_requests
limit: 0, model: gemini-2.5-pro
교훈: Preview 버전의 Pro 모델은 무료 쿼트가 거의 없는 경우가 있습니다. 검증 단계에서는 Gemini 2.5 Flash 를 사용하세요.
| 모델 | 무료 쿼트 |
|---|---|
| gemini-2.5-pro (가정)※ | ❌ 없음 |
| Gemini 2.5 Flash | |
| ✅ 15req/분, 1500req/일 |
다음 보안 조치를 구현했습니다.
| 조치 | 내용 |
|---|---|
| API 키 관리 | 스크립트 속성으로 관리 (코드에 하드코딩하지 않음) |
| ... |
【오류 처리의 생각】
❌ 나쁜 예 (사용자에게 상세 표시):
「Error: API key AIzaSy... is invalid」
...
GAS 에디터에서 「프로젝트 설정」→「스크립트 속성」에 다음을 등록하세요.
| 속성명 | 확인 위치 |
|---|---|
GEMINI_API_KEY | |
| https://aistudio.google.com/apikey | |
DRIVE_FOLDER_ID | |
Drive 폴더 URL 의 /folders/ 뒤 | |
VECTOR_SHEET_ID | |
스프레드시트 URL 의 /d/ 뒤 |
1. GAS 에디터에서「initialSetup」실행 → 헤더 생성 등
2. Google Drive 폴더에 자료 업로드 (2~3 파일)
3. 「rebuildEmbeddings」실행 → 벡터화
...
고침 포인트⑥: clasp open 가 사용 불가
Unknown command "clasp open"
clasp 의 새 버전에서는 open
명령어가 폐지되었습니다.
해결책: .clasp.json
의 scriptId 를 사용하여 직접 URL 열기.
1. GAS 에디터에서「배포」→「새로운 배포」
2. 종류: 웹 앱
3. 다음 사용자로서 실행: 나
...
| 항목 | 내용 |
|---|---|
| 자료 | 침술·동양의학 강의 자료 (2~3 파일) |
| ... |
질문: 「鼓脹의 주증은 무엇입니까?」
답변: (TODO: 실제 답변 텍스트 붙기)
정확도:まあ悪くない 🟡
...
| 비교 항목 | Dify 버전 | GAS 버전 |
|---|---|---|
| 답변 속도 | TODO: 실측치 교체 (예: 평균 2.1 초) | TODO: 실측치 교체 (예: 평균 7.8 초) |
| ... |
상세는次回記事(学習記録 #6)에서 다룹니다.
현재 정확도는「まあ悪くない」수준입니다. 여기서부터 정확도를 높이기 위한 개선 계획을 세웠습니다.
| 레벨 | 주제 | 개요 |
|---|---|
Level 1 |
チャンク 전략의 개선 | Chunk 사이즈 최적화·세맨틱 체인킹·메타데이터 충실 |
Level 2 |
프로ンプ트의 개선 | Few-shot 추가·답변 형식 지정·할루시네이션 억제 강화 |
Level 3 |
검색 정확도의 개선 | Rerank 모델 도입·HyDE·쿼리 확장·Top K 동적 조정 |
Level 4 |
평가의 체계화 | 테스트 세트 생성·자동 평가 스크립트·A/B 테스트·로그 분석 |
前後編を通じて의 고침 포인트 목록입니다.
| # | 고침 | 오류 메시지 | 해결책 |
|---|---|
| 1 | clasp 권한 승인 | (브라우저 승인 화면) | 「모든 선택」→「계속」 |
| 2 | webapp 지정 불가 | Invalid container file type |
--type standalone 변경 |
| 3 | GAS API 미활성화 | User has not enabled the Apps Script API |
설정 페이지에서 API 온 |
| 4 | TS 타입 주석 오류 | Unexpected token ':' |
clasp 3.x 는 TypeScript 를 컴파일하지 않음. tsc 로 사전 컴파일 후 dist/ 에서 push (상세는 전편 참조) |
| 5 | 마니페스트 덮어쓰기 | Do you want to push and overwrite? |
y 허용 |
| 6 | clasp open 불가 | Unknown command "clasp open" |
scriptId 로 직접 URL 생성 (→ 2-2 참조) |
| 7 | Gemini Pro 무료 쿼트 | 429 RESOURCE_EXHAUSTED |
Gemini 2.5 Flash 변경 (→ 1-4 참조) |
GAS × Gemini 로 RAG 챗봇을自作한 전 기록을 요약했습니다.
Dify 버전과 GAS 버전의 2 개의 RAG 챗봇을同日에 구축 및 테스트 완료 - GAS + TypeScript + clasp 개발환경 구축
Gemini Embedding (gemini-embedding-001) 으로 벡터화
Google Sheets 를 벡터 DB 로 사용
Gemini 2.5 Flash 로 LLM 답변 생성
Web App 으로 배포하고, 브라우저에서 챗 가능하게
- RAG 의 전 과정을「손맛」으로 이해함 → Embedding, 벡터 검색, 컨텍스트 구축의 메커니즘
- clasp 3.x 는 TypeScript 를 컴파일하지 않음...
정확도 개선 계획의 Level 1 (테스트 세트 제작 + 프롬프트 개선) 에서 시작 예정임.
- [【학습 기록 #5 전편】GAS × Gemini 로 RAG 챗봇을自作した ― 환경構築・つまずきポイント編](TODO: 전편 기타 URL 을 붙임)
- GAS + Gemini 로 만드는社内 RAG 챗봇 - Qiita(ogaryo 님)
- Google Apps Script 공식
- Gemini API 문서
- Gemini Embedding API
- clasp GitHub
다음회予告: [학습 기록 #6] 정밀도 개선 사이클 — 테스트 세트 제작과 프롬프트 최적화
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기