ANTIGRAVITY LABEN
記事一覧/AIツール
AIツール/2026-03-26上級

Antigravity で構築する RAG パイプライン — ベクトル検索 × LLM で社内ナレッジを即座に活用する

Antigravity でベクトル検索パイプラインを構築する上級チュートリアル。gemini-embedding-001 への移行で実際につまずいた次元数・正規化・タスクタイプの3点、ChromaDB への差分インジェスト、ハイブリッド検索、LLM-as-a-Judge による評価までを実装コードとともに解説します。

rag8vector-search3embeddingllm3antigravity449ai-tools15chromadbadvanced19

プレミアム記事

取り組みの背景 — RAG の概念を整理しつつ、なぜ今必要なのか

LLM(大規模言語モデル)は汎用的な知識を持っていますが、自社固有の情報——社内ドキュメント、API仕様書、ナレッジベース、顧客対応履歴——には対応できません。ファインチューニングは高コストで、データが更新されるたびに再学習が必要です。

RAG(Retrieval-Augmented Generation) はこの課題を解決するアーキテクチャです。ユーザーの質問に関連するドキュメントをベクトル検索で取得し、その情報をコンテキストとして LLM に渡すことで、正確で最新の回答を生成します。

Antigravity の AI 支援を使いながら、RAG パイプラインをゼロから組み立てていきます。想定読者は、Python の基礎があり、自分たちのデータの上で LLM を動かしたいエンジニアの方です。プロンプト設計に不安が残る場合は、先にAntigravity プロンプトエンジニアリング上級ガイドへ目を通していただくと、後半の生成フェーズが読みやすくなります。

ひとつ先にお伝えしておきたいことがあります。本記事のコードは Embedding モデルに gemini-embedding-001 を使用しています。長らく RAG のサンプルコードで定番だった text-embedding-004 は 2026年1月14日に停止済みで、いま呼び出しても 404 が返るだけです。移行はモデル名を1行書き換えるだけに見えて、次元数・正規化・タスクタイプの3点でつまずきます。私が実際に踏んだ順に、記事の後半で節を立ててまとめました。

RAG アーキテクチャの全体設計

RAG システムは大きく3つのフェーズで構成されます。

インジェストフェーズ(データ取り込み)

ドキュメントを読み込み、適切なサイズのチャンクに分割し、Embedding モデルでベクトル化してデータベースに格納します。

# Antigravity で生成した RAG インジェストパイプライン
# document_ingestor.py
 
import hashlib
import math
import os
from pathlib import Path
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import (
    DirectoryLoader,
    TextLoader,
    PyPDFLoader,
    UnstructuredMarkdownLoader,
)
from langchain_google_genai import GoogleGenerativeAIEmbeddings
import chromadb
from chromadb.config import Settings
 
# --- 設定 ---
CHROMA_PERSIST_DIR = "./chroma_db"
COLLECTION_NAME = "company_knowledge_v2"  # 次元数を変えたら名前も変える
CHUNK_SIZE = 800       # トークン数ではなく文字数
CHUNK_OVERLAP = 200    # チャンク間のオーバーラップ
EMBED_MODEL = "gemini-embedding-001"
EMBED_DIM = 1536       # 推奨値は 768 / 1536 / 3072。3072 以外は自前で正規化する
 
def load_documents(source_dir: str) -> list:
    """複数形式のドキュメントを一括読み込み"""
    loaders = {
        "**/*.txt": TextLoader,
        "**/*.md": UnstructuredMarkdownLoader,
        "**/*.pdf": PyPDFLoader,
    }
    documents = []
    for glob_pattern, loader_cls in loaders.items():
        loader = DirectoryLoader(
            source_dir,
            glob=glob_pattern,
            loader_cls=loader_cls,
            show_progress=True,
        )
        documents.extend(loader.load())
    return documents
 
def chunk_documents(documents: list) -> list:
    """ドキュメントを意味的に適切なサイズに分割"""
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP,
        separators=["\n## ", "\n### ", "\n\n", "\n", "。", ".", " "],
        length_function=len,
    )
    return splitter.split_documents(documents)
 
def l2_normalize(vec: list[float]) -> list[float]:
    """3072 次元未満を要求すると正規化されていないベクトルが返る"""
    norm = math.sqrt(sum(v * v for v in vec))
    return [v / norm for v in vec] if norm else vec
 
def chunk_id(chunk) -> str:
    """ソースと本文から安定した ID を作る(再実行しても重複しない)"""
    source = chunk.metadata.get("source", "unknown")
    seed = f"{source}::{chunk.page_content}".encode("utf-8")
    return hashlib.sha1(seed).hexdigest()
 
def create_embeddings_and_store(chunks: list):
    """Embedding を生成し ChromaDB に格納"""
    embeddings = GoogleGenerativeAIEmbeddings(
        model=EMBED_MODEL,
        google_api_key=os.getenv("GOOGLE_API_KEY"),
        output_dimensionality=EMBED_DIM,
    )
 
    client = chromadb.PersistentClient(
        path=CHROMA_PERSIST_DIR,
        settings=Settings(anonymized_telemetry=False),
    )
 
    collection = client.get_or_create_collection(
        name=COLLECTION_NAME,
        metadata={"hnsw:space": "cosine"},  # コサイン類似度
    )
 
    batch_size = 100  # Gemini API の1リクエスト上限
    for i in range(0, len(chunks), batch_size):
        batch = chunks[i:i + batch_size]
        texts = [chunk.page_content for chunk in batch]
        metadatas = [chunk.metadata for chunk in batch]
        ids = [chunk_id(chunk) for chunk in batch]
 
        # 文書側は RETRIEVAL_DOCUMENT を明示する
        vectors = embeddings.embed_documents(
            texts,
            task_type="RETRIEVAL_DOCUMENT",
        )
        if EMBED_DIM != 3072:
            vectors = [l2_normalize(v) for v in vectors]
 
        # add ではなく upsert。同じ本文を二度入れても増えない
        collection.upsert(
            ids=ids,
            documents=texts,
            embeddings=vectors,
            metadatas=metadatas,
        )
        print(f"  格納完了: {i + len(batch)}/{len(chunks)} チャンク")
 
    print(f"✅ 全 {len(chunks)} チャンクを ChromaDB に反映しました")
 
# --- 実行 ---
if __name__ == "__main__":
    docs = load_documents("./knowledge_base")
    print(f"📄 {len(docs)} ドキュメントを読み込みました")
 
    chunks = chunk_documents(docs)
    print(f"✂️  {len(chunks)} チャンクに分割しました")
 
    create_embeddings_and_store(chunks)

期待される出力:

📄 47 ドキュメントを読み込みました
✂️  312 チャンクに分割しました
  格納完了: 100/312 チャンク
  格納完了: 200/312 チャンク
  格納完了: 300/312 チャンク
  格納完了: 312/312 チャンク
✅ 全 312 チャンクを ChromaDB に反映しました

ID を連番ではなく本文のハッシュにしている点を補足させてください。連番だと、ドキュメントを1件追加しただけで以降のチャンクの ID がすべてずれます。同じ本文が別 ID で二重登録され、検索結果の上位が同一内容で埋まる、という壊れ方をします。ハッシュにしておけば、内容が変わらないチャンクは何度流しても同じ ID に落ち着きます。add ではなく upsert を使っているのも同じ理由です。

差分更新を実装するときも、この ID 設計がそのまま土台になります。前回のハッシュ一覧を保存しておき、今回の一覧との差だけを upsert、消えた ID を delete する。それだけでインクリメンタルインジェストが成立します。

検索フェーズ(リトリーバル)

ユーザーの質問をベクトル化し、類似度の高いチャンクを検索します。ここでの精度が RAG 全体の品質を左右します。

生成フェーズ(ジェネレーション)

検索結果をプロンプトに組み込み、LLM が回答を生成します。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
RAG アーキテクチャの設計原則と構成要素を体系的に理解できる
Antigravity 上でベクトル検索パイプラインを構築し、社内データを即座にLLM回答に反映できる
本番運用で必要なチャンク戦略・リランキング・評価メトリクスの実装手法を習得できる
gemini-embedding-001 への移行時に踏みやすい次元数・正規化・タスクタイプの罠を事前に回避できる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

AIツール2026-03-30
Antigravity × カスタムAIチャットボット — RAG・Function Calling・Streaming UI の実装記録
RAG・Function Calling・Streaming UI を組み合わせたカスタムAIチャットボットの実装記録。日本語でリコールが伸びなかった原因(トークン見積もりの罠)と、埋め込みモデル選定・本番運用の判断まで動くコードで示します。
AIツール2026-06-12
Gemma 4 × Antigravity で組む検索品質の評価ハーネス
RAG の回答が時々ズレる問題を数値に置き換える評価ハーネスの作り方。30問のゴールデンデータセット、recall@5・MRR・nDCG の実装、Gemma 4 による忠実性審査、pytest の回帰テストまで、ローカル環境の実測値と共に共有します。
AIツール2026-05-11
アーティストとして Antigravity を制作補助に3ヶ月使って見えてきたもの
アプリ開発と並行してアート活動を続ける個人クリエイターが、Antigravity をアート制作の周辺作業に3ヶ月使った記録。創作そのものではなく『作品を世に出す作業』のどこを委ね、どこは手放さなかったか。委譲の判断軸を正直にまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →