Writing hybrid search queries#

以下のパターンは、ベクトル検索とキーワードおよび関係フィルターとのサポートされているすべての組み合わせをカバーしています。ネイティブ全文検索FTSを必要とするパターンを使用する前に、関連する設定ページを完了します。

ベクトル+関係フィルター#

ソーステーブルの任意の列でセマンティック検索結果をフィルタリングします。フィルタはベクトル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)::vector 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)::vector
LIMIT 10;

Vector +ネイティブPostgreSQL FTS#

標準のPostgreSQL tsvector / ts_rank をキーワードレッグとして使用します。この例では、ソーステーブルにsearch_vector tsvector 列を想定しており、 Setting up native PostgreSQL FTS の説明に従って移入およびインデックスが作成されます。

SET hnsw.iterative_scan = relaxed_order;

SELECT s.id, s.title,
       kb.value <=> aidb.kb_query_encode(public.pipeline_docs, connection pooling)::vector AS distance,
       ts_rank_cd(s.search_vector, plainto_tsquery(english, connection pooling))           AS fts_score
FROM public.pipeline_docs AS kb
JOIN my_docs AS s ON s.id::TEXT = kb.source_id
WHERE s.search_vector @@ plainto_tsquery(english, connection pooling)  -- keyword filter
ORDER BY kb.value <=> aidb.kb_query_encode(public.pipeline_docs, connection pooling)::vector
LIMIT 10;

WHERE 句は、ハードキーワードフィルタとして機能します。検索用語を含む行のみが検討されます。結果は意味的な類似性によって並べられます。両方だけでなくいずれかの信号と一致する結果を表示するには、代わりに 重み付けリニアフュージョン を使用します。

重み付けリニアフュージョン#

加重線形フュージョンは、正規化されたベクトルの類似性とフルテキストスコアを、2つの明示的な重み1つがセマンティック関連性、もう1つがキーワード関連性を使用して単一の複合スコアに組み合わせます。合計が1.0になる必要があります。ランク位置のみを使用するRRFとは異なり、重み付けリニアフュージョンは実際のスコアの大きさを尊重するため、各信号が最終的なランキングにどの程度寄与するかを正確に制御したい場合に役立ちます。

重み付けリニアフュージョンとRRFを使用する場合#

Weighted linear fusion

RRF

Uses actual score values

Yes

No (rank positions only)

Requires score normalization

Yes

No

Tunable per use case

Yes (explicit weights)

Partially (smoothing constant k)

Robust to score scale differences

No (normalize carefully)

Yes (rank-based, scale-independent)

Best for

Workloads where score magnitude carries information

General-purpose merging of heterogeneous result sets

ワークロードに基づいて重みを調整します。

Use case

Suggested weights

Concept/meaning is most important

0.7 semantic, 0.3 keyword

Exact terminology matters as much as meaning

0.5 semantic, 0.5 keyword

Keyword precision is critical (for example, code or rule search)

0.3 semantic, 0.7 keyword

ネイティブPostgreSQL FTSの場合

ベクトル距離とts_rank 値は異なるスケールを使用するため、両方のレッグは結合する前に[0, 1] に正規化されます。コサイン距離レンジ[0, 2] は、(2.0 - distance) / 2.0 を使用して類似度に変換されます。 ts_rank_cd は既におよそ[0, 1] の値を返しています。

WITH semantic AS (
    SELECT source_id,
           (2.0 - (value <=> aidb.kb_query_encode(public.pipeline_docs, connection pooling)::vector)) / 2.0
               AS vec_score
    FROM public.pipeline_docs
    ORDER BY value <=> aidb.kb_query_encode(public.pipeline_docs, connection pooling)::vector
    LIMIT 50
),
keyword AS (
    SELECT id::TEXT AS source_id,
           ts_rank_cd(search_vector, plainto_tsquery(english, connection pooling)) AS fts_score
    FROM my_docs
    WHERE search_vector @@ plainto_tsquery(english, connection pooling)
    ORDER BY fts_score DESC
    LIMIT 50
)
SELECT
    COALESCE(s.source_id, k.source_id)                             AS id,
    0.6  *COALESCE(s.vec_score, 0) + 0.4*  COALESCE(k.fts_score, 0) AS fusion_score
FROM semantic s
FULL OUTER JOIN keyword k ON s.source_id = k.source_id
ORDER BY fusion_score DESC
LIMIT 10;

実用的な例リレーショナル述語フィルタリング#

この例は、セマンティックベクトル検索とリレーショナルWHERE フィルターを組み合わせて、結果を特定のカテゴリに絞り込む方法を示しています。パイプラインは製品名をナレッジベースに埋め込み、クエリによってカテゴリでフィルターされた意味的に同様の結果を取得します。

ソーステーブルとパイプラインの設定

  1. ソーステーブルを作成します。

CREATE TABLE products (
    id           SERIAL PRIMARY KEY,
    product_name TEXT NOT NULL,
    category     TEXT NOT NULL
);
  1. サンプルデータを入力します。

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);
  1. 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
                          )
);
  1. パイプラインを実行してエンベディングを生成します。

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 | pipeline_name | intermediate_steps
- ----+---------------+--------------------+----------+---------------+--------------------
 8   | Veggie Burger | 0.9955745765192575 | {0}      | food_products | []
 6   | Pizza         | 1.0280680840689795 | {0}      | food_products | []
 4   | Fries         | 1.0630385212203115 | {0}      | food_products | []
 7   | Sandwich      | 1.0903594734917037 | {0}      | food_products | []

リレーショナルフィルターを使用したカスタムクエリー

このクエリは、ソーステーブルにWHERE 句を追加して、結果を単一のカテゴリに制限し、ベクトルORDER BY を同じクエリレベルに保つため、反復インデックススキャンがLIMIT を効率的に満たすことができます。

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)::vector
LIMIT 10;
__OUTPUT__
 source_id | product_name  |  category
- ----------+---------------+------------
 8         | Veggie Burger | vegetarian
 4         | Fries         | vegetarian

ベジタリアン商品のみが返され、「ファーストフード」との意味的な関連性によって順序付けられます。