執行混合型向量相似度搜尋

AlloyDB for PostgreSQL 的混合型搜尋功能結合關鍵字全文搜尋和語意向量搜尋,可提高搜尋關聯性。關鍵字搜尋會找出完全相符的字詞,而向量搜尋則會找出與查詢語意相似的結果,即使沒有完全相符的關鍵字也沒問題。

您可以建立全文搜尋索引 (例如 GIN、RUM 或 BM25 索引),以及 ScaNN 或 HNSW 等向量索引,然後使用倒數排名融合 (RRF) 演算法,合併兩種搜尋類型的結果並重新排名。

如要進行高效率或高關聯性的文字搜尋,可以建立及查詢 RUM 索引,或建立及管理 BM25 索引。

您也可以將 AlloyDB 向量搜尋結果,與下列支援的外部搜尋引擎權杖搜尋結果合併:

在 AlloyDB 執行混合型搜尋的方法有很多種。請參閱下表,根據您的用途選擇最合適的方法:

做法 說明 用途
SQL 函式 hybrid_search() 是內建函式,可使用 RRF 結合向量和 Text Search 搜尋結果,簡化混合型搜尋。 如果需要直接在 SQL 中執行混合式搜尋,建議採用這個做法。
原始 SQL 查詢 手動建構 SQL 查詢,分別執行向量和文字搜尋,並使用 RRF 合併結果。 需要完全控管查詢邏輯,或需要實作超出 hybrid_search() 函式功能的自訂排名。
LangChain 整合功能 使用 LangChain 中的 AlloyDBVectorStore 類別執行混合型搜尋。 您正在使用 LangChain 架構建構 Python 應用程式,並想整合 AlloyDB 做為具備混合型搜尋功能的向量儲存庫。

事前準備

如要在 AlloyDB 執行混合型搜尋,請在資料表建立向量索引和文字搜尋索引。接著,系統會合併兩項搜尋的結果,並重新排序,呈現最相關的資訊。

建立 GIN 索引

一般化反向索引 (GIN) 是一種專門用於搜尋複合值 (例如陣列、JSONB 和全文搜尋資料) 的索引類型。

如要在文字資料上建立 GIN 索引,以便執行全文搜尋,請執行下列指令:

CREATE INDEX INDEX_NAME ON TABLE USING GIN (to_tsvector('english', COLUMN_NAME));

更改下列內容:

  • INDEX_NAME:要建立的索引名稱,例如 my_gin_index。

  • TABLE:要新增索引的資料表。

  • COLUMN_NAME:儲存要搜尋文字資料的資料欄。

建立 ScaNN 索引

如要使用 ScaNN 演算法,將兩層樹狀結構索引套用至含有已儲存向量嵌入的資料欄,請執行下列 DDL 查詢:

CREATE INDEX INDEX_NAME ON TABLE
  USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION)
  WITH (num_leaves=NUM_LEAVES_VALUE);

更改下列內容:

  • INDEX_NAME:要建立的索引名稱,例如 my_scann_index。索引名稱會在資料庫中共用。請確保資料庫中每個資料表的索引名稱不重複。

  • TABLE:要新增索引的資料表。

  • EMBEDDING_COLUMN:儲存 vector 資料的資料欄。

  • DISTANCE_FUNCTION:要與這個索引搭配使用的距離函式。選擇下列其中一個選項:

    • L2 距離: l2

    • 點積: dot_product

    • 餘弦距離: cosine

  • NUM_LEAVES_VALUE:要套用至這個索引的分區數量。PostgreSQL 17 以上版本可設為 1 至 104,857,600 之間的任何值,PostgreSQL 16 以下版本則可設為 1 至 3, 000 萬之間的值。如要進一步瞭解如何決定這個值,請參閱「調整 ScaNN 索引」。

如要進一步瞭解不同的 ScaNN 索引設定,請參閱「建立 ScaNN 索引」。

ai.hybrid_search() 函式可讓您合併多種搜尋類型的結果,例如向量搜尋和全文搜尋。這項函式會使用 RRF 演算法,將各個搜尋元件的排名結果合併為一份統合清單。這種做法比單一搜尋類型更能提供相關結果。

hybrid_search() 函式會動態建構及執行單一 SQL 查詢。系統會為您定義的每個搜尋元件建立一般資料表運算式 (CTE)。接著,函式會彙整所有 CTE 的結果,並計算每份文件的最終 RRF 分數,產生統一的排名清單。

準備資料並建立索引

使用 hybrid_search 函式前,請先準備資料並建立必要索引。

  1. 建立資料表來儲存文件。

    CREATE TABLE documents (
      doc_id TEXT PRIMARY KEY,
      content TEXT,
      text_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
      text_embedding vector(3072) GENERATED ALWAYS AS (embedding('gemini-embedding-001', content)) STORED 
    );
    
  2. 插入資料。

    INSERT INTO documents (doc_id, content) VALUES
      ('doc1', 'AlloyDB is a fully managed, PostgreSQL-compatible database service.'),
      ('doc2', 'It offers enterprise-grade performance, availability, and security.'),
      ('doc3', 'You can use it for demanding transactional and analytical workloads.'),
      ('doc4', 'AlloyDB integrates with Google Cloud services like Gemini Enterprise Agent Platform.'),
      ('doc5', 'The database supports vector embeddings for semantic search.'),
      ('doc6', 'alloydb_scann is an AlloyDB specific extension that provides scann index for vector search.'),
      ('doc7', 'alloydb_scann extension depends upon pgvector extension '),
      ('doc8', 'With alloydb_scann extension'),
      ('doc9', 'customers can create scann index'),
      ('doc10', 'to speed up their vector search workloads');
    
  3. 建立索引,加快搜尋速度。如要進行向量搜尋,請建立 scann 索引。如要進行全文搜尋,請建立 GIN 索引。

    CREATE EXTENSION IF NOT EXISTS alloydb_scann;
    CREATE INDEX documents_text_embedding_idx
    ON documents USING scann (text_embedding cosine)
    WITH(num_leaves = 10, quantizer = 'SQ8');
    
    CREATE INDEX documents_text_tsv_idx ON documents USING GIN (text_tsv);
    

呼叫 hybrid_search 函式並查看輸出範例

如要瞭解 hybrid_search 函式接受的參數,以便控制搜尋和融合程序,請參閱「混合搜尋函式參數」。

  1. 呼叫 hybrid_search 函式,合併向量和全文搜尋結果。這個步驟會合併搜尋結果,這些結果是透過執行使用者搜尋輸入內容定義的查詢取得。

    SELECT *
    FROM ai.hybrid_search(
      search_inputs => ARRAY[
          '{
            "data_type": "vector",
            "weight": 0.5,
            "table_name": "documents",
            "key_column": "doc_id",
            "vec_column": "text_embedding",
            "distance_operator": "public.<=>",
            "limit": 5,
            "query_vector": "ai.embedding(''gemini-embedding-001'', ''managed database'')::vector"
          }'::JSONB,
          '{
            "data_type": "text",
            "weight": 0.5,
            "table_name": "documents",
            "key_column": "doc_id",
            "text_column": "text_tsv",
            "limit": 5,
            "ranking_function": "ts_rank",
            "query_text_input": "database"
          }'::JSONB
      ],
      include_json_output => false
    );
    

    include_json_output 是選用參數。詳情請參閱「混合搜尋函式參數」。

  2. 查看輸出內容。

    如果 include_json_output 為 false,輸出內容會包含文件 ID 和最終分數。

      id  |        score
    ------+----------------------
     doc1 | 0.016393442622
     doc5 | 0.016129032258
     doc3 | 0.007936512936
     doc2 | 0.007812504999
     doc4 | 0.007692312692
    (5 rows)
    

    如果 include_json_output 為 true,輸出內容會包含 detail_json 欄,其中列出各個元件的分數計算明細。

      id  |        score         |                                                                                                                                                detail_json
    ------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
     doc1 |  0.01639344262 | {"item_id": "doc1", "calculation": {"component_1": {"rank": 1, "weight": 0.5, "data_type": "vector", "component_score": 0.01639344262295081967, "execute_time_ms": 5}, "component_2": {"rank": 1, "weight": 0.5, "data_type": "text", "component_score": 0.01639344262295081967, "execute_time_ms": 4}}, "final_score": 0.01639344262295082}
     doc5 | 0.016129032258 | {"item_id": "doc5", "calculation": {"component_1": {"rank": 2, "weight": 0.5, "data_type": "vector", "component_score": 0.01612903225806451613, "execute_time_ms": 5}, "component_2": {"rank": 2, "weight": 0.5, "data_type": "text", "component_score": 0.01612903225806451613, "execute_time_ms": 4}}, "final_score": 0.016129032258064516}
     ...
    

指定最終傳回類型的資料類型

id_type 參數可讓您指定最終傳回類型的資料類型。AlloyDB AI 會自動執行轉換。

舉例來說,如果 doc_id 欄是 TEXT,而您想將其轉換為 INTEGER,請將 NULL::INTEGER 傳遞至 id_type 參數。

CREATE TABLE product_logs (
    log_id_str TEXT PRIMARY KEY,
    content TEXT,
    content_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);

INSERT INTO product_logs (log_id_str, content)
VALUES ('999', 'system start and services initialized');

CREATE INDEX idx_product_logs_rum ON product_logs USING rum (content_tsv rum_tsvector_ops);

SELECT
    id,
    pg_typeof(id)
FROM ai.hybrid_search(
    ARRAY['{
        "data_type": "text",
        "table_name": "product_logs",
        "key_column": "log_id_str",
        "text_column": "content_tsv",
        "query_text_input": "system",
        "limit": 1
    }'::jsonb],
    id_type => NULL::INTEGER
);

輸出內容顯示 id 欄已轉換為 INTEGER:

  id  | pg_typeof
------+-----------
 999  | integer
(1 rows)

以下範例說明為何必須指定傳回 ID 欄的資料類型,並顯示不相符時會發生的情況。

DROP TABLE IF EXISTS product_logs;
CREATE TABLE product_logs (
    log_id_str TEXT PRIMARY KEY,
    content TEXT,
    content_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);
INSERT INTO product_logs VALUES ('999', 'system start');

SELECT
    id,
    pg_typeof(id),
    score
FROM ai.hybrid_search(
    ARRAY['{
        "data_type": "text",
        "table_name": "product_logs",
        "key_column": "log_id_str",
        "text_column": "content_tsv",
        "query_text_input": "system",
        "limit": 1
    }'::jsonb],
    id_type => NULL::INTEGER --- CAST to INT
);

選擇文字搜尋查詢剖析器

執行全文搜尋時,AlloyDB 會提供 g_to_tsquery() 函式,擷取高度相關的資訊。g_to_tsquery() 是預設值,可將純文字或標準 tsquery 格式轉換為資料更豐富的 tsquery 輸出內容,改善資訊檢索。

如果您偏好使用 PostgreSQL 剖析器函式,可以明確指定下列函式:

使用原始 SQL 執行混合式搜尋

混合型搜尋會分別執行向量和 Text Search,然後使用倒數排名融合 (RRF) 合併結果並重新排名。RRF 是以排名為基礎的演算法,會為每份文件指派分數,將多份搜尋結果排名清單合併為一份排名清單。這項分數是根據 RRF 在所有貢獻清單中的倒數排名計算得出,排名較高的文件貢獻度較高。

以下範例說明如何結合全文搜尋和混合型搜尋,並重新排序結果。

        WITH vector_search AS (
            SELECT id,
                RANK () OVER (ORDER BY embedding <=> ai.embedding('MODEL_ID', 'TEXT')) AS rank
                FROM TABLE
                ORDER BY embedding <=> ai.embedding('MODEL_ID', 'TEXT') LIMIT 10
        ),
        text_search AS (
            SELECT id,
                RANK () OVER (ORDER BY ts_rank(to_tsvector('english', COLUMN_NAME), to_tsquery(KEYWORD)) desc)
            FROM TABLE
            WHERE to_tsvector('english', COLUMN_NAME) @@ to_tsquery(KEYWORD)
            ORDER BY ts_rank(to_tsvector('english', COLUMN_NAME), to_tsquery(KEYWORD)) desc
            LIMIT 10
        )
        SELECT
            COALESCE(vector_search.id, text_search.id) AS id,
            COALESCE(1.0 / (60 + vector_search.rank), 0.0) + COALESCE(1.0 / (60 + text_search.rank), 0.0) AS rrf_score
        FROM vector_search FULL OUTER JOIN text_search ON vector_search.id = text_search.id
        ORDER BY rrf_score DESC
        LIMIT 5;

更改下列內容:

  • MODEL_ID:要查詢的模型 ID。

    如果使用 Model Garden,請指定 gemini-embedding-001 做為模型 ID。這些是 AlloyDB 可用於文字嵌入的雲端模型。詳情請參閱「文字嵌入」。

  • TABLE:包含資料的資料表。

  • TEXT:要轉換成向量嵌入的文字。

  • KEYWORD:要搜尋的關鍵字。

  • COLUMN_NAME:包含要搜尋的文字資料。

混合型搜尋查詢和相關一般資料表運算式 (CTE) 的說明:

  • vector_search CTE:執行標準向量相似度搜尋,依餘弦距離排序結果,並指派排名。並擷取語意最相似的前 10 項產品。
  • text_search CTE:使用 to_tsvector 和 to_tsquery 執行 Text Search,並以 ts_rank 計算關聯性,擷取關聯性最高的前 10 個相符文字。
  • Final SELECT Statement CTE:使用 FULL OUTER JOIN 彙整向量和文字搜尋結果、選取產品 ID、計算 RRF 分數、依分數排序,並擷取前 5 項結果。

使用原始 SQL 和 BM25 索引執行混合型搜尋

如果使用 pg_textsearch 擴充功能,可以運用 BM25 索引,在混合相似度搜尋查詢中取得高準確度的評分。

如要先取得最相關的比對結果,請以遞增 (ASC) 順序排序文字搜尋 CTE。BM25 運算子 (<@>) 會傳回負分數,也就是說,結果的關聯性越高,分數就越低。

下列範例說明如何執行混合型向量相似度搜尋,並結合 BM25 搜尋:

WITH vector_search AS (
    SELECT id,
        RANK () OVER (ORDER BY embedding <=> ai.embedding('MODEL_ID', 'TEXT')) AS rank
        FROM TABLE
        ORDER BY embedding <=> ai.embedding('MODEL_ID', 'TEXT') LIMIT 10
),
text_search AS (
    SELECT id,
        RANK () OVER (ORDER BY COLUMN_NAME <@> 'KEYWORD' ASC) AS rank
    FROM TABLE
    ORDER BY COLUMN_NAME <@> 'KEYWORD' ASC
    LIMIT 10
)
SELECT
    COALESCE(vector_search.id, text_search.id) AS id,
    COALESCE(1.0 / (60 + vector_search.rank), 0.0) + COALESCE(1.0 / (60 + text_search.rank), 0.0) AS rrf_score
FROM vector_search FULL OUTER JOIN text_search ON vector_search.id = text_search.id
ORDER BY rrf_score DESC
LIMIT 5;

更改下列內容:

  • MODEL_ID:要查詢的模型 ID。

    如果您使用 Agent Platform Model Garden,請指定 gemini-embedding-001 做為模型 ID。這些是 AlloyDB 可用於文字嵌入的雲端模型。詳情請參閱「文字嵌入」。

  • TABLE:包含資料的資料表。

  • TEXT:要轉換成向量嵌入的文字。

  • KEYWORD:要搜尋的關鍵字。

  • COLUMN_NAME:包含要搜尋的文字資料。

例如:

WITH vector_search AS (
    SELECT doc_id,
        RANK () OVER (ORDER BY text_embedding <=> ai.embedding('gemini-embedding-001', 'database')) AS rank
    FROM documents
    ORDER BY text_embedding <=> ai.embedding('gemini-embedding-001', 'database') LIMIT 10
),
text_search AS (
    SELECT doc_id,
        RANK () OVER (ORDER BY content <@> 'database' ASC) AS rank
    FROM documents
    ORDER BY content <@> 'database' ASC
    LIMIT 10
)
SELECT
    COALESCE(vector_search.doc_id, text_search.doc_id) AS doc_id,
    COALESCE(1.0 / (60 + vector_search.rank), 0.0) + COALESCE(1.0 / (60 + text_search.rank), 0.0) AS rrf_score
FROM vector_search FULL OUTER JOIN text_search ON vector_search.doc_id = text_search.doc_id
ORDER BY rrf_score DESC
LIMIT 5;

BM25 混合型搜尋查詢說明:

  • vector_search CTE:執行標準向量相似度搜尋,使用 gemini-embedding-001 模型對文字查詢 'database' 進行餘弦距離 (<=>) 排序,並指派排名。並擷取語意最相似的前 10 份文件。
  • text_search CTE:使用 BM25 <@> 運算子對 content 資料欄執行 Text Search,計算 'database' 的 BM25 關聯性分數,並依遞增 (ASC) 順序排序,將高度相關的文件排在前面,然後擷取前 10 個相符項目。
  • Final SELECT Statement:使用 FULL OUTER JOIN 彙整向量搜尋和文字搜尋結果、計算各文件 ID 的倒數排名融合 (RRF) 分數、依 RRF 分數遞減排序合併結果,並擷取前 5 份文件。

使用 LangChain 執行混合型搜尋

使用 AlloyDB 向量儲存庫進行混合搜尋,可結合兩種不同的查詢策略:密集嵌入向量搜尋和關鍵字搜尋,進而提高搜尋準確度。AlloyDBVectorStore 是 LangChain 向量儲存庫類別,可做為 LangChain VectorStore 類別的特定實作,使用 LangChain。瞭解如何使用 AlloyDBVectorStore 類別,透過 AlloyDB 儲存向量嵌入。

設定 AlloyDBVectorStore 時,您可以使用 HybridSearchConfig 類別啟用及設定這項混合搜尋功能。

使用 AlloyDB 向量儲存庫進行混合搜尋時,系統會同時執行語意搜尋 (瞭解查詢的意義和脈絡) 和關鍵字搜尋 (找出完全相符的字詞)。接著,系統會合併兩項搜尋的結果,提供更全面的結果。

後續步驟