ChromaDBのセットアップ:RAG向けローカルベクトルストレージの実践ガイド

Database tutorial - IT technology blog
Database tutorial - IT technology blog

AIアプリケーションにおけるパフォーマンスのボトルネック

RAG(検索拡張生成)システムの構築は、特定のデータに基づいてLLMに回答させたいというシンプルな目標から始まることが多いものです。例えば、5,000件の技術PDFや1年分のサーバーログがある場合を考えてみましょう。多くの開発者は、まずこれらのテキストをMySQLのTEXTカラムやMongoDBのドキュメントに保存しようとします。そして、LIKE演算子や標準的な全文検索を使って関連するコンテキストを見つけようとします。

しかし、このアプローチは実務では大抵うまくいきません。キーワード検索は文字通りで柔軟性に欠けるからです。ユーザーが「接続の問題(connectivity issues)」について尋ねた場合、標準的なデータベースは「ネットワーク遅延のトラブルシューティング(Network Latency Troubleshooting)」というタイトルのドキュメントを、単語が一致しないという理由だけで無視してしまう可能性があります。その結果、LLMには無関係なコンテキストが渡され、ハルシネーション(もっともらしい嘘)やレスポンスの低下を招きます。これこそが、従来のデータベースアーキテクチャが現代のAIのニーズに応えるのに苦労している点です。

なぜ従来のデータベースはベクトルに弱いのか

問題の原因はデータのインデックス方法にあります。PostgreSQLやMongoDBなどのデータベースは、B-Treeやハッシュインデックスを使用して、文字列、整数、日付などのスカラー値を処理することに長けています。これらは、完全一致や特定の範囲内の値を検索するのに最適です。

しかし、AIモデルは「埋め込み(embeddings)」を通じて世界を見ています。これは、高次元空間におけるセマンティックな(意味的な)意味を表す浮動小数点数の配列です。例えば、OpenAIのtext-embedding-3-smallモデルは、1,536次元のベクトルを生成します。

関連データを見つける際、必要なのは「一致」ではなく、ベクトル間の数学的な距離の計算です。数百万行にわたってこれらの計算を実行するのは計算負荷が非常に高くなります。標準的なデータベースはこれに最適化されていないため、データセットが増えるにつれて、クエリのレイテンシが数秒を超えてしまうことがよくあります。

ローカル開発に適したツールの選択

ベクトルストアを選択する際には、いくつかの道があります。Pineconeのようなクラウドネイティブなオプションは優れたスケーリングを提供しますが、月額費用やデータプライバシーのトレードオフが伴います。一方で、QdrantやWeaviateのような重量級のツールは強力ですが、通常は複雑なDockerクラスターの管理が必要です。これは、ローカルでプロトタイプを作成したり、小規模な社内ツールを構築したりする開発者にとっては過剰(オーバーキル)かもしれません。

ChromaDBはシンプルさを重視して設計されています。これはオープンソースのAIネイティブなデータベースであり、「バッテリー同梱(必要なものがすべて揃った)」ソリューションとして機能します。インポートを1つ記述するだけで、Pythonスクリプト内で実行できます。開始するために個別のサーバープロセスを必要としないため、ラップトップやプライベートなエッジサーバーでRAGアプリケーションを実行するための現実的な選択肢となります。

ChromaDBを始める

ChromaDBのセットアップは非常に簡単です。クリーンな環境から機能するベクトルストアを構築するまで、5分もかかりません。実装を見ていきましょう。

インストールとセットアップ

Python 3.8以上が必要です。pipを使用してコアパッケージをインストールします。他のAIライブラリとのバージョン競合を避けるため、仮想環境を使用するのがベストプラクティスです。

pip install chromadb

ChromaDBにはデフォルトの埋め込みモデル(all-MiniLM-L6-v2)が含まれています。これにより、外部のAPIキーやアクティブなインターネット接続を必要とせずに、すぐにテキストのインデックス作成と検索を開始できます。

最初のコレクションの作成

ChromaDBのコレクションは、SQLのテーブルに似ています。関連するドキュメントとそれに対応するベクトルをグループ化します。データをローカルディスクに保存するクライアントを初期化する方法は次のとおりです。

import chromadb

# ディスク永続化を有効にしてクライアントを初期化
# ベクトルを保存するためのローカルフォルダが作成されます
client = chromadb.PersistentClient(path="./my_vector_db")

# コレクションを作成、または既存のものをロード
collection = client.get_or_create_collection(name="tech_support_kb")

ドキュメントとメタデータの追加

テキストを追加すると、ChromaDBが自動的にベクトル化を処理します。また、メタデータも含めるべきです。これにより、後で「ソース」や「バージョン」などの特定の属性で結果をフィルタリングできるようになり、検索速度が向上します。

collection.add(
    documents=[
        "セカンダリノードで30秒のタイムアウトが発生したため、データベース接続に失敗しました。",
        "クエリ速度を向上させるには、すべての外部キーがインデックス化されていることを確認してください。",
        "認証エラーは、有効期限が切れたJWTトークンによって頻繁に発生します。"
    ],
    metadatas=[
        {"source": "system_logs", "severity": "critical"},
        {"source": "best_practices", "severity": "low"},
        {"source": "auth_service", "severity": "medium"}
    ],
    ids=["log_001", "doc_042", "err_99"]
)

セマンティックな意味によるクエリ

真の力はセマンティック検索にあります。以下の例では、クエリに「タイムアウト」という言葉は含まれていませんが、ChromaDBは「レスポンスが遅すぎる」という概念に基づいて、最初のドキュメントを最も関連性の高い一致として正しく特定します。

results = collection.query(
    query_texts=["サーバーのレスポンスに時間がかかりすぎているのはなぜですか?"],
    n_results=1
)

print(results["documents"])

ストレージの管理と本番環境へのスケーリング

クイックテスト以外の用途では、デフォルトのインメモリクライアントの使用は避けてください。スクリプトが停止した瞬間にデータが消失します。PersistentClientを使用することで、ChromaDBはデータをSQLiteデータベースとParquetファイルとして保存します。これにより、アプリケーションを再起動してもベクトルが保持されます。

スケールアップが必要な場合や、複数のサービスから同じデータにアクセスできるようにする場合は、Docker経由でスタンドアロンサービスとしてChromaDBを実行します。

docker run -p 8000:8000 chromadb/chroma

その後、PythonクライアントでサーバーのURLを指定するだけです。

client = chromadb.HttpClient(host='localhost', port=8000)

カスタム埋め込みモデルの使用

デフォルトのモデルは高速ですが軽量です。精度を高めたい場合は、OpenAIのtext-embedding-3-smallやHuggingFaceの特定のモデルを使用するのが良いでしょう。ChromaDBでは、これらをコレクションレベルで簡単に切り替えることができます。

from chromadb.utils import embedding_functions

openai_ef = embedding_functions.OpenAIEmbeddingFunction(
                api_key="YOUR_API_KEY", # あなたのAPIキー
                model_name="text-embedding-3-small"
            )

collection = client.get_or_create_collection(
    name="high_precision_docs", 
    embedding_function=openai_ef
)

一度定義すれば、ライブラリがすべての変換を管理します。データを挿入したり検索したりするたびに、手動でOpenAI APIを呼び出す必要はありません。

本番環境に向けた実践的なアドバイス

ベクトルデータベースの管理には、従来のデータベース管理とは異なる考え方が必要です。現場から得られた4つの教訓を紹介します。

  • 賢いチャンク分割 (Smart Chunking): 50ページのドキュメントを丸ごと埋め込まないでください。長いテキストではセマンティックな意味が失われます。ドキュメントを500〜800トークンのチャンクに分割し、チャンク間のコンテキストを維持するために10〜15%のオーバーラップを設けます。
  • メタデータによる事前フィルタリング: メタデータを使用して検索範囲を絞り込みます。「2024年」のドキュメントのみが必要な場合、ベクトル検索の前にメタデータフィルタを適用することで、レイテンシを大幅に削減し、精度を向上させることができます。
  • 一貫したID: コンテンツのSHA-256ハッシュなど、決定論的なIDを使用します。これにより、取り込みパイプラインを複数回実行しても、重複エントリが発生するのを防げます。
  • RAMの監視: ChromaDBはインデックス作成にHNSW(Hierarchical Navigable Small World)を使用します。これは非常に高速ですが、メモリを大量に消費します。ベクトルが数十万件に増えるにつれて、インデックスを保持するのに十分なRAMが環境にあることを確認してください。

ChromaDBは、コンセプトから機能するRAGのプロトタイプを作成するための最短経路を提供します。インフラ管理の摩擦を取り除き、AIのロジックに集中できるようにしてくれます。プロジェクトが成長しても、ここで学ぶ概念(コレクション、埋め込み、セマンティック距離)は、より複雑なエンタープライズシステムにそのまま応用できるでしょう。

Share: