🔎 AI幻覚を解決する:ハイブリッド検索 (Hybrid Search) 実践
前章では、ドキュメントのチャンキング (Chunking)、ベクトル変換 (Embeddings)、そしてベクトルデータベースを使った「意味検索 (Vector/Semantic Search)」を学びました。
最初はこの技術に驚いたはずです! 「アップル社の最新決算」と検索すると、「Apple 2023 Q4 収益」のドキュメントが見つかります。AIが「アップル社」と「Apple」の意味的類似性を理解しているからです。
しかし、このシステムを実際の顧客に提供すると災難が訪れます。
1. ベクトル検索 (Vector Search) の致命的弱点
純粋な意味検索が苦手とするものがあります:**「固有名詞」や「製品型番」**です。
例:データベースに以下の2つのドキュメントがある場合
- ドキュメント A:
製品型番 XG-999-Pro は高性能ゲーミングノートPCで、32GBメモリを搭載... - ドキュメント B:
製品型番 XG-998-Lite は薄型軽量のビジネスノートPC...
ユーザーが「XG-999-Pro 仕様」と検索した時、どちらがヒットするでしょうか? 答え:両方とも類似と判定される可能性が高い! 場合によってはドキュメントBの方が高スコアになることも!
なぜか?AIの頭(Embedding空間)では、XG-999-ProとXG-998-Liteは「見慣れない英数字の組み合わせ」であり、意味的に非常に近いと判断されるからです。AIはProの有無が重要な違いだとは理解できません。
この場合、古くても正確な**「キーワード検索 (Keyword Search / BM25)」**が最も有効です。文字列が一致しなければスコアは0になります!
2. ハイブリッド検索 (Hybrid Search) とは?
意味検索が「意味」を理解し、キーワード検索が「文字列」を理解するなら、両者を組み合わせれば良いのではないか?
ハイブリッド検索 (Hybrid Search) のコンセプトはシンプルです:
- ユーザーのクエリに対して、まず「意味検索」を実行し、Top 10のリストを取得(スコア例:0.8)
- 同時に、従来の「キーワード検索 (BM25)」も実行し、別のTop 10リストを取得(スコア例:0.9)
- 両リストを**RRF (Reciprocal Rank Fusion, 逆順位融合)**アルゴリズムに渡す
- RRFが両方のスコアを重み付け計算し、最終的な「最強のTop 5リスト」を生成
これは現在、業界の最先端RAGシステム(Pinecone、Supabase Vectorなど)の標準的な手法です。
3. Supabaseでのハイブリッド検索実装
Supabaseをベクトルデータベースとして使用している場合、ハイブリッド検索の実装は簡単です。PostgreSQLは全文検索 (Full-Text Search) をネイティブでサポートしています。
手順1:キーワード検索インデックスの作成
Supabase SQLエディタで、ドキュメントテーブルに全文検索用のカラムを追加:
-- documentsテーブルにfts(全文検索)カラムを追加
alter table documents add column fts tsvector generated always as (to_tsvector('english', content)) stored;
-- 検索用インデックスを作成
create index on documents using gin (fts);
手順2:ハイブリッド検索用ストアドプロシージャの作成
Supabase内で両方の検索を実行し、スコアを統合するRPC (Remote Procedure Call) 関数:
create or replace function match_documents_hybrid(
query_embedding vector(1536), -- OpenAIのベクトル
query_text text, -- 元のキーワード
match_count int, -- 取得件数
full_text_weight float default 1, -- キーワード検索の重み
semantic_weight float default 1 -- 意味検索の重み
)
returns table (
id uuid,
content text,
similarity float
)
language plpgsql
as $$
begin
return query
with semantic_search as (
-- 1. 意味検索 (Cosine Similarity)
select documents.id, documents.content, 1 - (documents.embedding <=> query_embedding) as semantic_score
from documents
order by documents.embedding <=> query_embedding
limit match_count * 2
),
keyword_search as (
-- 2. キーワード検索 (Full Text Search)
select documents.id, documents.content, ts_rank(documents.fts, websearch_to_tsquery('english', query_text)) as keyword_score
from documents
where documents.fts @@ websearch_to_tsquery('english', query_text)
order by keyword_score desc
limit match_count * 2
)
-- 3. スコア統合 (簡易加重法)
select
coalesce(semantic_search.id, keyword_search.id) as id,
coalesce(semantic_search.content, keyword_search.content) as content,
-- 両方のスコアを加算 (正規化が必要な場合あり、ここでは簡略化)
(coalesce(semantic_search.semantic_score, 0.0) * semantic_weight +
coalesce(keyword_search.keyword_score, 0.0) * full_text_weight) as similarity
from semantic_search
full outer join keyword_search on semantic_search.id = keyword_search.id
order by similarity desc
limit match_count;
end;
$$;
手順3:LangChainやNext.jsからの呼び出し
ユーザーの検索クエリを受け取ったら、このRPCを呼び出します:
import { createClient } from '@supabase/supabase-js';
import { OpenAIEmbeddings } from 'langchain/embeddings/openai';
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
const embeddings = new OpenAIEmbeddings();
async function performHybridSearch(question: string) {
// 1. 質問をベクトル化
const queryEmbedding = await embeddings.embedQuery(question);
// 2. ハイブリッド検索RPCを呼び出し
const { data, error } = await supabase.rpc('match_documents_hybrid', {
query_embedding: queryEmbedding, // 意味検索用
query_text: question, // キーワード検索用
match_count: 5, // トップ5を取得
full_text_weight: 1.2, // キーワード検索の重みを高く設定
semantic_weight: 1.0
});
if (error) {
console.error("検索失敗:", error);
return [];
}
return data;
}
4. 上級テクニック:Cohere Rerank
SQLでのスコア統合が面倒、または精度不足と感じる場合、業界では**Rerank (再ランキング)**という手法が使われています。
手順:
- 通常のベクトル検索で上位20件のドキュメントを取得
- これらのドキュメントとユーザーの質問を、スコアリング専門のAIモデル(例:
Cohere Rerank)に送信 - AIが20件のドキュメントを読み、「質問に実際に答えられるか」で再ランキング
- 上位3件をGPTに渡して最終回答を生成
LangChainでCohere Rerankを統合する例:
import { CohereRerank } from "@langchain/cohere";
import { ContextualCompressionRetriever } from "langchain/retrievers/contextual_compression";
// 1. ベースのベクトル検索器を設定(例:pineconeやsupabase)
const baseRetriever = vectorStore.asRetriever(20); // 20件取得
// 2. Cohereの圧縮/再ランキング器を設定
const compressor = new CohereRerank({
apiKey: process.env.COHERE_API_KEY,
model: "rerank-multilingual-v2.0", // 日本語対応!
topN: 3 // 再ランキング後はトップ3のみ
});
// 3. 最強の検索器に組み立て
const hybridRetriever = new ContextualCompressionRetriever({
baseCompressor: compressor,
baseRetriever: baseRetriever,
});
// 4. 検索実行!
const docs = await hybridRetriever.getRelevantDocuments("XG-999-Pro の冷却仕様は?");
Hybrid SearchやRerankを導入すると、AIの回答精度が70%から95%以上に急上昇します。 「明らかな型番を間違える」といった顧客の不満はなくなります。これが「おもちゃ」と「商用製品」を分ける核心技術です。