キーワードだけではユーザーを「理解」できなくなった時
半年ほど前、私はかなり手強いタスクを引き受けました。それは、5万件以上の法律文書を対象とした検索ツールの構築です。当初、PostgreSQLで従来の全文検索(Full-text search)を使用しましたが、結果は散々なものでした。ユーザーが「税金に関する規定」と検索すればシステムは正常に機能しますが、「国に対する財政的義務」と入力すると、これら二つの概念がほぼ同一であるにもかかわらず、結果はゼロでした。
問題は、従来のデータベースが文字ベース(lexical)でしかデータを処理できない点にあります。これを解決するために、FAISSライブラリを使用して純粋なベクトル検索(Vector Search)に切り替えてみました。しかし、ベクトル検索には逆の弱点がありました。文脈の理解には長けているものの、「通達 123/2023」のような特定の文書番号といった完全一致の結果を見落とすことが多々あったのです。
なぜベクトル検索だけでは不十分なのか?
試行錯誤を繰り返した結果、現代的なRAG(Retrieval-Augmented Generation)システムには、正確な検索のためのキーワード検索と、文脈を理解するためのベクトル検索の両方の融合が必要であると気づきました。ここでハイブリッド検索(Hybrid Search)がその真価を発揮します。
もしベクトルデータベースをゼロから自作しようとすれば、次の3つの大きな落とし穴に直面することになります:
- テキストとベクトルの間のデータ同期が非常に複雑であること。
- 埋め込みモデル(OpenAI、HuggingFace)の管理に多くのコードリソースを消費すること。
- データが数百万件に達した際の拡張性の確保。
私は以前QdrantやPineconeも使用しましたが、柔軟性とデータセキュリティのためのセルフホスト(オンプレミス)を優先するプロジェクトにおいて、最終的な選択肢となったのはWeaviateでした。
Weaviate:AIアプリケーションのためのストレージエコシステム
Weaviateは単なるベクトルストレージではありません。6ヶ月の実戦投入を経て感じた最大のメリットは、統合されたAIモジュールです。APIを呼び出してベクトルを取得してからDBに保存するというコードをわざわざ書く必要はなく、Weaviateがvectorizersを通じてこのプロセスを自動的に実行してくれます。
そのハイブリッド検索メカニズムは、BM25アルゴリズムとベクトル検索をalphaという重み付けによって柔軟に組み合わせます。これこそが、以前直面した複雑な法律文書検索の課題を根本的に解決してくれた「武器」でした。
WeaviateをDockerで5分で構築する
迅速かつ安定したデプロイには、Dockerが最適な選択肢です。以下は、Weaviateにtext2vec-openaiモジュールを組み合わせて実行するために簡素化したdocker-compose.ymlファイルです。
version: '3.4'
services:
weaviate:
command:
- --host
- 0.0.0.0
- --port
- '8080'
- --scheme
- http
image: semitechnologies/weaviate:1.24.1
ports:
- 8080:8080
- 50051:50051
restart: on-failure:0
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'text2vec-openai'
ENABLE_MODULES: 'text2vec-openai,generative-openai,qna-openai'
CLUSTER_HOSTNAME: 'node1'
OPENAI_APIKEY: 'sk-xxxxxxxxxxxxxxxxxxxxxxxx' # あなたのAPIキーに置き換えてください
ちょっとしたコツ:無料で試したい場合は、text2vec-openaiをtext2vec-transformersに変更して、ローカルで埋め込みモデルを実行してください。その後、次のコマンドでシステムを起動します:
docker-compose up -d
Pythonクライアントでハイブリッド検索を実行する
コンテナの準備ができたら、weaviate-clientライブラリを使用して操作します。以下は、スキーマの定義と実際のハイブリッド検索クエリの実行方法です。
import weaviate
import json
client = weaviate.Client("http://localhost:8080")
# 1. スキーマの初期化
class_obj = {
"class": "Document",
"vectorizer": "text2vec-openai",
"properties": [
{"name": "title", "dataType": ["text"]},
{"name": "content", "dataType": ["text"]}
]
}
if not client.schema.exists("Document"):
client.schema.create_class(class_obj)
# 2. ハイブリッド検索のクエリ
# alpha = 1.0: 純粋なベクトル検索 | alpha = 0.0: 純粋なキーワード検索
query_text = "国に対する財政的義務"
result = (
client.query
.get("Document", ["title", "content"])
.with_hybrid(query=query_text, alpha=0.5)
.with_limit(3)
.do()
)
print(json.dumps(result, indent=2, ensure_ascii=False))
6ヶ月間の運用から得た実戦経験
Weaviateをプロダクション環境に導入する際、ドキュメントでは見落とされがちな技術的詳細がいくつかあります。システムが突然停止するのを防ぐために、以下の点に注意してください。
1. リソース管理の課題
ベクトルデータベースは非常に多くのRAMを消費します。100ms以下の検索速度を保証するために、HNSWインデックスは常にメモリ上に配置されます。OpenAI의 1536次元のベクトルを100万件扱う場合、少なくとも16GBのRAMを用意することをお勧めします。Product Quantization(PQ)圧縮を使用すれば、精度と引き換えにこれを4〜8GBまで抑えることが可能です。
2. バックアップ戦略
Dockerのデータフォルダをコピーするだけで済ませないでください。WeaviateはS3やGCS専用のバックアップモジュールをサポートしています。私は以前、ディスク故障でデータをすべて失いかけましたが、S3にあったバックアップのおかげで救われました。
3. Alpha値の微調整
alphaに万能な公式はありません。法律データの場合、私は文脈を優先して0.7に設定しました。逆に、SKUコードの検索が必要なECサイトのデータでは、0.3まで下げることが多いです。サンプルデータセットでテストを行い、「スイートスポット」を見つける必要があります。
4. Generative ModuleによるRAGの最適化
generative-openaiモジュールを活用しましょう。検索結果を取得してからGPT-4に送信するのではなく、Weaviateに1つのクエリ内で回答を要約させることができます。この方法により、ネットワーク遅延が大幅に短縮され、コードもすっきりします。
おわりに
Weaviateは強力なツールですが、魔法の杖ではありません。インデックスを正しく構成するには、データの特性を深く理解する必要があります。Dockerによるデプロイは、AIアプリケーションをデモ環境からプロフェッショナルな実用環境へと引き上げるための完璧な足がかりとなるでしょう。

