hybrid-retrieval について
hybrid-retrievalは、RAGシステムを構築する開発者向けのスキルです。ベクトル類似度とキーワード検索を組み合わせる方法を扱い、結果順を整えるリランキングも紹介します。
主な機能
- 01ベクトル検索とキーワード検索の併用
- 02Reciprocal Rank Fusionの紹介
- 03LangChainとLlamaIndexのパターン
- 04Qdrantハイブリッド検索の例
- 05クロスエンコーダーとCohereのリランキング
- 06重み調整と評価の指針
活用シーン
- 01製品コードを含む文書の検索
- 02コーパスから言い換えられた概念を検索
- 03技術文書や法律文書向けの検索調整
- 04ハイブリッド検索結果のリランキング
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ latestaiagents/ agent-skills --skill hybrid-retrievalインストール後、Claude Code で/hybrid-retrievalを入力して読み込まれたか確認します。
詳細
RAG 向けハイブリッド検索
ベクトル類似度とキーワードマッチングを組み合わせ、検索精度を大幅に高めます。
2026 年にハイブリッド検索が必須である理由
ベクトル検索だけでは、次のものを見逃します。
- 完全一致(製品コード、ID、名前)
- 埋め込み表現で十分に捉えられていない希少な用語
- キーワードに特化したクエリ(「エラーコード E-5012」)
キーワード検索だけでは、次のものを見逃します。
- 意味的な類似性(「車」と「自動車」)
- 文脈と意味
- 言い換えられたコンテンツ
ハイブリッド検索では両方を組み合わせることで、再現率が 15〜25%向上します。
基本パターン
パターン 1:相互順位融合(RRF)
複数の検索器による順位付き結果を組み合わせる標準的な手法です。
def reciprocal_rank_fusion(
results_lists: list[list[dict]],
k: int = 60
) -> list[dict]:
"""
Combine multiple ranked result lists using RRF.
Args:
results_lists: List of ranked results from different retrievers
k: Ranking constant (default 60, higher = more weight to lower ranks)
Returns:
Fused and re-ranked results
"""
fused_scores = {}
for results in results_lists:
for rank, doc in enumerate(results):
doc_id = doc["id"]
if doc_id not in fused_scores:
fused_scores[doc_id] = {"doc": doc, "score": 0}
# RRF formula: 1 / (k + rank)
fused_scores[doc_id]["score"] += 1 / (k + rank + 1)
# Sort by fused score
sorted_results = sorted(
fused_scores.values(),
key=lambda x: x["score"],
reverse=True
)
return [item["doc"] for item in sorted_results]
パターン 2:LangChain Ensemble Retriever
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Chroma
# Create vector retriever
vectorstore = Chroma.from_documents(documents, embeddings)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
# Create BM25 retriever
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 10
# Combine with weights
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6] # Tune based on your data
)
# Use in RAG chain
results = ensemble_retriever.invoke("your query here")
パターン 3:LlamaIndex ハイブリッド検索
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.core.retrievers import QueryFusionRetriever
from llama_index.retrievers.bm25 import BM25Retriever
# Build index
index = VectorStoreIndex.from_documents(documents)
# Create retrievers
vector_retriever = index.as_retriever(similarity_top_k=10)
bm25_retriever = BM25Retriever.from_defaults(
nodes=index.docstore.docs.values(),
similarity_top_k=10
)
# Fusion retriever with query expansion
retriever = QueryFusionRetriever(
retrievers=[vector_retriever, bm25_retriever],
similarity_top_k=10,
num_queries=4, # Generate 4 query variations
mode="reciprocal_rerank",
use_async=True,
)
パターン 4:Qdrant による直接実装
from qdrant_client import QdrantClient
from qdrant_client.models import SparseVector, NamedSparseVector, NamedVector
client = QdrantClient(url="http://localhost:6333")
# Hybrid search with both dense and sparse vectors
results = client.query_points(
collection_name="documents",
prefetch=[
# Dense vector search
models.Prefetch(
query=dense_embedding, # [0.1, 0.2, ...]
using="dense",
limit=20
),
# Sparse vector search (BM25-style)
models.Prefetch(
query=SparseVector(
indices=[1, 42, 123], # Token IDs
values=[0.5, 0.8, 0.3] # Token weights
),
using="sparse",
limit=20
),
],
query=models.FusionQuery(fusion=models.Fusion.RRF),
limit=10
)
精度を高めるリランキング
ハイブリッド検索の後、最終的な順序になるようリランキングします。
クロスエンコーダーによるリランキング
from sentence_transformers import CrossEncoder
# Load reranker model
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank_results(query: str, documents: list[str], top_k: int = 5):
"""Rerank documents using cross-encoder."""
# Create query-document pairs
pairs = [[query, doc] for doc in documents]
# Score all pairs
scores = reranker.predict(pairs)
# Sort by score
scored_docs = list(zip(documents, scores))
scored_docs.sort(key=lambda x: x[1], reverse=True)
return [doc for doc, score in scored_docs[:top_k]]
Cohere Rerank API
import cohere
co = cohere.Client("your-api-key")
def cohere_rerank(query: str, documents: list[str], top_k: int = 5):
"""Rerank using Cohere's rerank endpoint."""
response = co.rerank(
model="rerank-english-v3.0",
query=query,
documents=documents,
top_n=top_k,
return_documents=True
)
return [result.document.text for result in response.results]
重み調整のガイドライン
| データの種類 | ベクトルの重み | キーワードの重み |
|---|---|---|
| 技術ドキュメント | 0.5 | 0.5 |
| 法務/コンプライアンス | 0.4 | 0.6 |
| クリエイティブコンテンツ | 0.7 | 0.3 |
| 製品カタログ | 0.3 | 0.7 |
| コードリポジトリ | 0.4 | 0.6 |
ベストプラクティス
- 必ずベンチマークを実施する - お使いのデータで、ベクトルのみ、キーワードのみ、ハイブリッドをテストします
- 重みを実測に基づいて調整する - 0.5/0.5 から始め、評価に基づいて調整します
- リランキングを使用する - ハイブリッド検索 + リランキング = 最良の結果
- クエリの種類を考慮する - 完全一致クエリはキーワード検索に、意味検索クエリはベクトル検索に振り分けます
- レイテンシを監視する - ハイブリッド検索には追加の負荷がかかるため、可能な場合はキャッシュします
クイック判断フロー
Is the query an exact match (ID, code, name)?
├─ Yes → Keyword-heavy (0.3 vector / 0.7 keyword)
└─ No → Is it conceptual/semantic?
├─ Yes → Vector-heavy (0.7 vector / 0.3 keyword)
└─ Mixed → Balanced (0.5 / 0.5) + reranking
コメント
Markdown 対応