Writing hybrid search queries
=============================

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

- :ref:`Setting up native PostgreSQL FTS <Setting up native PostgreSQL FTS>` 

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

ソーステーブルの任意の列でセマンティック検索結果をフィルタリングします。フィルタはベクトル\ ``ORDER BY``
と同じクエリレベルで適用されるため、反復インデックススキャンで正確に\ ``LIMIT``
の結果を生成できます。

.. code:: sql

   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``
列を想定しており、
:ref:`Setting up native PostgreSQL FTS <Setting up native PostgreSQL FTS>` の説明に従って移入およびインデックスが作成されます。

.. code:: sql

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

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

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

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

.. csv-table::
  :header: "",Weighted linear fusion,RRF
  :widths: 12,15,25
  :align: left
  :class: longtable

  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

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

.. csv-table::
  :header: Use case,Suggested weights
  :widths: 25,20
  :align: left
  :class: longtable

  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]`` の値を返しています。

.. code:: sql

   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. ソーステーブルを作成します。

.. code:: sql

   CREATE TABLE products (
       id           SERIAL PRIMARY KEY,
       product_name TEXT NOT NULL,
       category     TEXT NOT NULL
   );

1. サンプルデータを入力します。

.. code:: sql

   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`` をナレッジベースに埋め込むパイプラインを作成します。

.. code:: sql

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

.. code:: sql

   SELECT aidb.run_pipeline(food_products);

**標準のセマンティッククエリーフィルタなし**

このベースラインクエリーは、制約なしで上位のセマンティック一致を返すため、結果セットを後続のフィルタリングされたクエリーと比較できます。

.. code:: sql

   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``
を効率的に満たすことができます。

.. code:: sql

   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

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