Hybrid search#
ハイブリッド検索は、ナレッジベースの埋め込みのセマンティックベクター/埋め込み検索と1つ以上の追加フィルターメカニズムを組み合わせます。
リレーショナル述語フィルタリング — ソーステーブルの任意の列、たとえば
WHERE category = 'vegetarian'やWHERE published_date > '2024-01-01'などで結果をフィルタリングします。BM25全文検索 BM25インデックスを使用してキーワードの関連性によって結果をランク付けし、特定の単語またはフレーズが結果に表示されるようにします。
これら2つのアプローチは、セマンティック検索と並行して、独立してまたは一緒に使用できます。
AIパイプラインは、埋め込み生成を含むナレッジベースの構築を通常のように処理します。ハイブリッド検索は、クエリー時に、ベクターテーブルとソーステーブルを組み合わせたカスタムSQLを構成することにより適用されます。
前提条件#
pgvector反復インデックススキャン#
pgvector 0.8.0以降が必要ですEDB Postgres AIのデフォルトは0.8.1です。
反復インデックススキャンは、ベクトルORDER BY とWHERE
フィルターを効率的に組み合わせるためのキーイネーブラーです。これらがないと、ベクトルインデックスはフィルタリング前に固定数の候補をプリフェッチします。つまり、選択性の高いフィルタがLIMIT
要求よりも少ない結果を返す場合があります。反復スキャンが有効になっている場合、インデックスはフィルタリング後でもLIMIT
が満足するまで候補を生成し続けます。
HNSWインデックスの反復スキャンを有効にします。
SET hnsw.iterative_scan = relaxed_order;
IVFFlatインデックスの場合
SET ivfflat.iterative_scan = on;
非常に選択的なフィルター一致する行がほとんどない場合、安全制限を設定して、スキャンがインデックス全体を走査しないようにします。
SET hnsw.iterative_scan_max_tuples = 20000;
重要
反復スキャンを有効にするには、ベクトル`ORDER BY` と`WHERE` フィルターが同じクエリレベルである必要がありますCTE境界で分離されていません。以下のクエリパターンは、それに応じて構造化されています。
BM25オプション#
BM25フルテキストフィルタリングには、 vchord_bm25
および pg_tokenizer 拡張機能が必要です。これらはユーザーがインストールおよび管理します。設定の詳細については、
VectorChord-BM25 を参照してください。
ヘルパーファンクションとメタデータ#
promoteb.kb_query_encode()#
モデル名を検索する必要なく、特定のナレッジベース用に構成された埋め込みモデルを使用してテキストクエリーをエンコードします。
SELECT aidb.kb_query_encode(public.my_kb, test query);
カスタムベクタークエリを構築するときに、
aidb.encode_text('<model>', '<query>') の代わりにこれを使用します。
aidb.knowledge_bases ビュー#
aidb.knowledge_bases ビュー aidb.kbs
としてもアクセス可能は、ハイブリッドクエリーの構成に必要なすべてのメタデータを公開します。
Column |
Type |
Description |
|---|---|---|
id |
integer |
内部識別子 |
name |
text |
ナレッジベース名 |
vector_schema |
text |
エンベディングテーブルのスキーマ |
vector_table |
text |
エンベディングストレージテーブル |
model_name |
text |
このKBで使用される埋め込みモデル |
distance_operator |
aidb.distanceoperator |
距離メトリックの列挙値 |
distance_operator_sql |
text |
SQL演算子構文の距離演算子 |
vector_data_column |
text |
エンベディングを格納する列 |
vector_key_column |
text |
埋め込みをソース行にリンクする列 |
vector_index |
jsonb |
ベクトルインデックス構成 |
pipeline_ids |
integer[] |
このナレッジベースに接続されているすべてのパイプラインのID |
pipeline_names |
text[] |
このナレッジベースに接続されているすべてのパイプラインの名前 |
SELECT name, vector_schema, vector_table, distance_operator_sql
FROM aidb.knowledge_bases;
promoteb.rerank_text()#
オプショナル 候補者を取得した後、最終的な関連性の高い結果を得るためのリランキングモデルに渡します。
SELECT aidb.rerank_text(my_reranking_model, query text, ARRAY[result1, result2, ...]);
nim_reranking やhf_tei_reranking
などの再ランキングモデルは、取得後の別の後処理ステップとしてサポートされています。
クエリパターン#
ベクトル+関係フィルター#
ソーステーブルの任意の列でセマンティック検索結果をフィルタリングします。フィルタはベクトルORDER BY
と同じクエリレベルで適用されるため、反復インデックススキャンで正確にLIMIT
の結果を生成できます。
SET hnsw.iterative_scan = relaxed_order;
SELECT s.id, s.description,
kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion) AS distance
FROM my_kb_vector kb
JOIN my_source_table s ON s.id::TEXT = kb.source_id
WHERE s.category_id = 2 -- relational filter on source table
ORDER BY kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion)
LIMIT 10;
Vector + BM25フィルタとして#
WHERE
条件としてBM25スコアしきい値を使用し、結果をベクトル距離で並べ替えます。これにより、キーワード一致も満たす、意味的に最も近い結果が返されます。
SET hnsw.iterative_scan = relaxed_order;
SELECT s.id, s.description,
kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion) AS distance
FROM my_kb_vector kb
JOIN my_source_table s ON s.id::TEXT = kb.source_id
WHERE s.bm25_col <&> to_bm25query(my_bm25_idx, tokenize(shoes, bert)) < -1.0 -- BM25 filter
ORDER BY kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion)
LIMIT 10;
Vector + BM25相互ランク融合経由#
Reciprocal Rank FusionRRFは、セマンティック検索とキーワード検索を独立して実行し、結果セットをマージおよびスコア付けします。これにより、オーバーフィルタリングが回避され、いずれのディメンションでも良好なランクの結果が表面化されます。
WITH semantic AS (
SELECT source_id AS id,
ROW_NUMBER() OVER (ORDER BY value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion) AS rank
FROM my_kb_vector
ORDER BY value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion)
LIMIT 50
),
fulltext AS (
SELECT id::TEXT AS id,
ROW_NUMBER() OVER (ORDER BY bm25_col <&> to_bm25query(my_bm25_idx, tokenize(shoes, bert))) AS rank
FROM my_source_table
ORDER BY bm25_col <&> to_bm25query(my_bm25_idx, tokenize(shoes, bert))
LIMIT 50
)
SELECT COALESCE(s.id, f.id) AS id,
COALESCE(1.0 / (60 + s.rank), 0) + COALESCE(1.0 / (60 + f.rank), 0) AS rrf_score
FROM semantic s
FULL OUTER JOIN fulltext f ON s.id = f.id
ORDER BY rrf_score DESC
LIMIT 10;
RRF式の定数60
は、標準のランク平滑化係数です。これを調整して、高ランクと低ランクの結果の相対的な重みを制御します。
Vector +リレーショナル+BM25すべて3つ#
3つのフィルタリングメカニズムすべてを単一のクエリーに組み合わせます。
SET hnsw.iterative_scan = relaxed_order;
SELECT s.id, s.description,
kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion) AS distance,
s.bm25_col <&> to_bm25query(my_bm25_idx, tokenize(shoes, bert)) AS bm25_score
FROM my_kb_vector kb
JOIN my_source_table s ON s.id::TEXT = kb.source_id
WHERE s.category_id = 2 -- relational filter
AND s.bm25_col <&> to_bm25query(my_bm25_idx, tokenize(shoes, bert)) < -1.0 -- BM25 filter
ORDER BY kb.value <=> aidb.kb_query_encode(public.my_kb_vector, summer fashion)
LIMIT 10;
実用的な例#
例1リレーショナル述語フィルタリング#
この例は、セマンティックベクトル検索とリレーショナルWHERE
フィルターを組み合わせて、結果を特定のカテゴリに絞り込む方法を示しています。パイプラインは製品名をナレッジベースに埋め込み、クエリによってカテゴリでフィルターされた意味的に同様の結果を取得します。
ソーステーブルとパイプラインの設定
ソーステーブルを作成します。
CREATE TABLE products (
id SERIAL PRIMARY KEY,
product_name TEXT NOT NULL,
category TEXT NOT NULL
);
サンプルデータを入力します。
INSERT INTO products (product_name, category) VALUES
(Hamburger, meat),
(Cheeseburger, meat),
(Fish n Chips, fish),
(Fries, vegetarian),
(Burrito, meat),
(Pizza, meat),
(Sandwich, meat),
(Veggie Burger, vegetarian),
(Kebab, meat);
product_name をナレッジベースに埋め込むパイプラインを作成します。
SELECT aidb.create_pipeline(
name => food_products,
source => products,
source_key_column => id,
source_data_column => product_name,
step_1 => KnowledgeBase,
step_1_options => aidb.knowledge_base_config(
model => bert,
data_format => Text
)
);
パイプラインを実行してエンベディングを生成します。
SELECT aidb.run_pipeline(food_products);
標準のセマンティッククエリーフィルタなし
SELECT * FROM aidb.retrieve_text(public.pipeline_food_products, fast food, topk => 4);
__OUTPUT__
key | value | distance | part_ids | intermediate_steps
- ----+---------------+--------------------+----------+--------------------
8 | Veggie Burger | 0.9955745765192575 | {0} | []
6 | Pizza | 1.0280680840689795 | {0} | []
4 | Fries | 1.0630385212203115 | {0} | []
7 | Sandwich | 1.0903594734917037 | {0} | []
リレーショナルフィルターを使用したカスタムクエリー
SET hnsw.iterative_scan = relaxed_order;
SELECT vector.source_id, source.product_name, source.category
FROM public.pipeline_food_products AS vector
JOIN products AS source ON source.id::TEXT = vector.source_id
WHERE source.category = vegetarian -- relational filter
ORDER BY vector.value <=> aidb.kb_query_encode(public.pipeline_food_products, fast food)
LIMIT 10;
__OUTPUT__
source_id | product_name | category
- ----------+---------------+------------
8 | Veggie Burger | vegetarian
4 | Fries | vegetarian
ベジタリアン商品のみが返され、「ファーストフード」との意味的な関連性によって順序付けられます。
例2 RRFを使用したBM25全文検索#
この例では、逆位ランク融合RRFを使用してセマンティックベクトル検索とBM25キーワード検索を組み合わせて、2つの結果セットをマージする方法を示しています。
RRFは、セマンティックランキングとキーワードランキングでの位置を組み合わせて結果を再ランク付けし、両方で高いスコアの項目を表示します。この例は、例1と同じproducts
テーブルとfood_products パイプラインに基づいて構築されています。
ソーステーブルにBM25インデックスを追加します
pg_tokenizer およびvchord_bm25 拡張機能を有効にします。
CREATE EXTENSION IF NOT EXISTS pg_tokenizer;
CREATE EXTENSION IF NOT EXISTS vchord_bm25;
BERTモデルを使用してトークナイザーを作成します。
SELECT tokenizer_catalog.create_tokenizer(bert, $$
model = "bert_base_uncased"
$$);
BM25ベクトル列をソーステーブルに追加し、トークン化された値を入力します。
ALTER TABLE products ADD COLUMN embedding_bm25 bm25_catalog.bm25vector;
UPDATE products SET embedding_bm25 = tokenizer_catalog.tokenize(product_name, bert);
新しい列にBM25インデックスを作成します。
CREATE INDEX products_bm25_idx ON products USING bm25 (embedding_bm25 bm25_catalog.bm25_ops);
RRFを使用したハイブリッドクエリ
SET hnsw.iterative_scan = relaxed_order;
SET search_path TO public, tokenizer_catalog, bm25_catalog;
WITH semantic AS (
SELECT source.id,
source.product_name,
source.category,
ROW_NUMBER() OVER (ORDER BY vector.value <=> aidb.kb_query_encode(public.pipeline_food_products, fast food) AS rank
FROM public.pipeline_food_products AS vector
JOIN products AS source ON source.id::TEXT = vector.source_id
ORDER BY vector.value <=> aidb.kb_query_encode(public.pipeline_food_products, fast food)
LIMIT 20
),
keyword AS (
SELECT id,
product_name,
ROW_NUMBER() OVER (ORDER BY embedding_bm25 <&> to_bm25query(products_bm25_idx, tokenize(fast food, bert))) AS rank
FROM products
ORDER BY embedding_bm25 <&> to_bm25query(products_bm25_idx, tokenize(fast food, bert))
LIMIT 20
)
SELECT
COALESCE(s.id, k.id) AS id,
COALESCE(s.product_name, k.product_name) AS product_name,
s.category,
s.rank AS semantic_rank,
k.rank AS keyword_rank,
COALESCE(1.0 / (60 + s.rank), 0) + COALESCE(1.0 / (60 + k.rank), 0) AS rrf_score
FROM semantic s
FULL OUTER JOIN keyword k ON s.id = k.id
ORDER BY rrf_score DESC
LIMIT 10;
__OUPUT__
id | product_name | category | semantic_rank | keyword_rank | rrf_score
- ---+---------------+------------+---------------+--------------+------------------------
1 | Hamburger | meat | 6 | 1 | 0.03154495777446597119
4 | Fries | vegetarian | 3 | 4 | 0.03149801587301587302
6 | Pizza | meat | 2 | 6 | 0.03128054740957966765
8 | Veggie Burger | vegetarian | 1 | 8 | 0.03109932497589199614
3 | Fish n Chips | fish | 7 | 3 | 0.03079838900734423123
5 | Burrito | meat | 5 | 5 | 0.03076923076923076924
2 | Cheeseburger | meat | 9 | 2 | 0.03062178588125292193
7 | Sandwich | meat | 4 | 7 | 0.03055037313432835821
9 | Kebab | meat | 8 | 9 | 0.02919863597612958227
RRFスコアは、両方のランキングを組み合わせたものです。セマンティックまたはキーワード検索サーフェスのいずれかで高ランクのアイテムが上部にあり、両方でよくランクされているアイテムが最高スコアを取得します。
ユーザー管理のBM25のセットアップ#
BM25は、AIパイプラインによって管理されません。ソーステーブルで独立して設定し、ハイブリッドクエリで参照します。
1.拡張機能のインストール
CREATE EXTENSION IF NOT EXISTS pg_tokenizer;
CREATE EXTENSION IF NOT EXISTS vchord_bm25;
2.トークナイザーを作成します
SELECT create_tokenizer(bert, $$
model = "bert_base_uncased"
$$);
3. ``bm25vector`` 列とインデックスをソーステーブルに追加します
ALTER TABLE my_source ADD COLUMN bm25_tokens bm25vector;
UPDATE my_source SET bm25_tokens = tokenize(description, bert);
CREATE INDEX my_source_bm25 ON my_source USING bm25 (bm25_tokens bm25_ops);
4.インデックスを最新の状態に保ちます
新しい行は、BM25結果に表示される前にトークン化する必要があります。データ取り込みプロセスの一部としてトリガーを使用するか、列を更新します。
UPDATE my_source SET bm25_tokens = tokenize(description, bert) WHERE bm25_tokens IS NULL;
トークナイザーの完全な構成オプションについては、 VectorChord-BM25 および VectorChord-BM25 in pg_extensions を参照してください。
ボリュームソースの制限#
リレーショナル述語フィルタリングまたはBM25を使用したハイブリッド検索は、 ボリュームソースのナレッジベースではサポートされていません 。ボリュームソースには、結合するバッキングPostgresテーブルがないため、フィルタまたはインデックスを作成するソーステーブルがありません。
ボリュームソースのナレッジベースは、 aidb.retrieve_text()
およびaidb.retrieve_key()
を介して標準のセマンティック検索を引き続きサポートします。