在 Bigtable 上使用 LangChain 執行最大邊際關聯性搜尋

本頁面說明如何使用 BigtableVectorStore 整合功能,在 Bigtable 和 Vertex AI 中執行 Maximal Marginal Relevance (MMR) 搜尋,並將 Vertex AI 做為嵌入服務。

MMR 是資訊檢索中使用的搜尋技術,可傳回一組與查詢相關且多樣化的結果,避免重複。標準向量相似度搜尋 (例如使用 k-最鄰近方法進行的搜尋) 可能會傳回許多相似項目,但 MMR 會提供更多樣化的熱門結果。如果向量儲存空間中可能出現重疊或重複的資料,這項功能就非常實用。

舉例來說,在電子商務應用程式中,如果使用者搜尋「紅番茄」,向量相似度搜尋可能會傳回多個相同類型的新鮮紅番茄商品。MMR 搜尋會盡量傳回更多樣的結果,例如「新鮮紅番茄」、「罐頭紅番茄丁」、「有機櫻桃番茄」,甚至可能包括「番茄沙拉食譜」。

閱讀本頁面之前,請務必瞭解下列概念:

  • 關聯性:文件與查詢的相符程度。
  • 多樣性:衡量文件與結果集中已選取文件的差異程度。
  • Lambda 乘數:介於 0 和 1 之間的係數,可平衡相關性和多樣性。值越接近 1,系統越會優先顯示相關性高的結果;值越接近 0,系統越會優先顯示多樣性高的結果。

LangChain 的 BigtableVectorStore 類別會將 MMR 實作為重新排序演算法。這項演算法會先擷取與查詢相關的一組較大的文件,然後以兼顧相關性和多元性的方式,選取符合查詢的文件。

事前準備

本指南使用 Vertex AI 做為嵌入服務。確認您已在專案中啟用 Vertex AI API。

啟用 Vertex AI API

必要的角色

如要搭配 LangChain 使用 Bigtable,您需要下列 IAM 角色:

設定環境

  1. 安裝必要的 LangChain 套件:

    pip install --upgrade --quiet langchain-google-bigtable langchain-google-vertexai
    
  2. 使用使用者帳戶向 Google Cloud 進行驗證。

    gcloud auth application-default login
    
  3. 設定專案 ID、Bigtable 執行個體 ID 和資料表 ID:

    PROJECT_ID = "your-project-id"
    INSTANCE_ID = "your-instance-id"
    TABLE_ID = "your-table-id"
    

初始化嵌入服務並建立表格

如要使用 Bigtable 向量儲存區,您需要提供 AI 模型生成的嵌入。在本指南中,您將使用 Vertex AI 上的文字嵌入 gemini-embedding-004 模型

from langchain_google_vertexai import VertexAIEmbeddings
from langchain_google_bigtable.vector_store import init_vector_store_table, BigtableVectorStore, ColumnConfig

# Initialize an embedding service
embedding_service = VertexAIEmbeddings(model_name="text-embedding-004", project=PROJECT_ID)

# Define column families
DATA_COLUMN_FAMILY = "product_data"

# Initialize the table (if it doesn't exist)
try:
    init_vector_store_table(
        project_id=PROJECT_ID,
        instance_id=INSTANCE_ID,
        table_id=TABLE_ID,
        content_column_family=DATA_COLUMN_FAMILY,
        embedding_column_family=DATA_COLUMN_FAMILY,
    )
    print(f"Table {TABLE_ID} created successfully.")
except ValueError as e:
    print(e) # Table likely already exists

例項化 BigtableVectorStore

傳遞嵌入服務和 Bigtable 資料表 ID,建立商店執行個體。

# Configure columns
content_column = ColumnConfig(
    column_family=DATA_COLUMN_FAMILY, column_qualifier="product_description"
)
embedding_column = ColumnConfig(
    column_family=DATA_COLUMN_FAMILY, column_qualifier="embedding"
)

# Create the vector store instance
vector_store = BigtableVectorStore.create_sync(
    project_id=PROJECT_ID,
    instance_id=INSTANCE_ID,
    table_id=TABLE_ID,
    embedding_service=embedding_service,
    collection="ecommerce_products",
    content_column=content_column,
    embedding_column=embedding_column,
)
print("BigtableVectorStore instantiated.")

填入向量儲存庫

在本指南中,我們將使用虛構的電子商務服務範例情境,使用者想搜尋與紅番茄相關的項目。首先,我們需要在向量儲存空間中加入一些番茄相關產品和說明。

from langchain_core.documents import Document

products = [
    Document(page_content="Fresh organic red tomatoes, great for salads.", metadata={"type": "fresh produce", "color": "red", "name": "Organic Vine Tomatoes"}),
    Document(page_content="Ripe red tomatoes on the vine.", metadata={"type": "fresh", "color": "red", "name": "Tomatoes on Vine"}),
    Document(page_content="Sweet cherry tomatoes, red and juicy.", metadata={"type": "fresh", "color": "red", "name": "Cherry Tomatoes"}),
    Document(page_content="Canned diced red tomatoes in juice.", metadata={"type": "canned", "color": "red", "name": "Diced Tomatoes"}),
    Document(page_content="Sun-dried tomatoes in oil.", metadata={"type": "preserved", "color": "red", "name": "Sun-Dried Tomatoes"}),
    Document(page_content="Green tomatoes, perfect for frying.", metadata={"type": "fresh", "color": "green", "name": "Green Tomatoes"}),
    Document(page_content="Tomato paste, concentrated flavor.", metadata={"type": "canned", "color": "red", "name": "Tomato Paste"}),
    Document(page_content="Mixed salad greens with cherry tomatoes.", metadata={"type": "prepared", "color": "mixed", "name": "Salad Mix with Tomatoes"}),
    Document(page_content="Yellow pear tomatoes, mild flavor.", metadata={"type": "fresh", "color": "yellow", "name": "Yellow Pear Tomatoes"}),
    Document(page_content="Heirloom tomatoes, various colors.", metadata={"type": "fresh", "color": "various", "name": "Heirloom Tomatoes"}),
]

vector_store.add_documents(products)
print(f"Added {len(products)} products to the vector store.")

商店中有許多番茄相關產品,但使用者只想搜尋與「紅番茄」相關的產品。如要取得多元的結果,請使用 MMR 技術執行搜尋。

主要方法是 max_marginal_relevance_search,會採用下列引數:

  • query (str):搜尋文字。
  • k (整數):最終搜尋結果數量。
  • fetch_k (int):套用 MMR 演算法前要擷取的類似產品初始數量。建議這個數字大於 k 參數。
  • lambda_mult (浮點數):多樣性調整參數。使用 0.0 可獲得最多樣化的結果,使用 1.0 則可獲得最相關的結果。
user_query = "red tomatoes"
k_results = 4
fetch_k_candidates = 10

print(f"Performing MMR search for: '{user_query}'")

# Example 1: Balanced relevance and diversity
mmr_results_balanced = vector_store.max_marginal_relevance_search(
    user_query, k=k_results, fetch_k=fetch_k_candidates, lambda_mult=0.5
)
print(f"MMR Results (lambda=0.5, k={k_results}, fetch_k={fetch_k_candidates}):")
for doc in mmr_results_balanced:
    print(f"  - {doc.metadata['name']}: {doc.page_content}")

print("\n")

# Example 2: Prioritizing Diversity
mmr_results_diverse = vector_store.max_marginal_relevance_search(
    user_query, k=k_results, fetch_k=fetch_k_candidates, lambda_mult=0.1
)
print(f"MMR Results (lambda=0.1, k={k_results}, fetch_k={fetch_k_candidates}):")
for doc in mmr_results_diverse:
    print(f"  - {doc.metadata['name']}: {doc.page_content}")

print("\n")

# Example 3: Prioritizing Relevance
mmr_results_relevant = vector_store.max_marginal_relevance_search(
    user_query, k=k_results, fetch_k=fetch_k_candidates, lambda_mult=0.9
)
print(f"MMR Results (lambda=0.9, k={k_results}, fetch_k={fetch_k_candidates}):")
for doc in mmr_results_relevant:
    print(f"  - {doc.metadata['name']}: {doc.page_content}")

不同的 lambda_mult 值會產生不同的結果組合,在與「紅番茄」的相似度與顯示產品的獨特性之間取得平衡。

搭配擷取器使用 MMR

如要使用 MMR 搜尋功能,您也可以設定 LangChain 擷取器。檢索器提供統一介面,可讓您將 MMR 等專業搜尋方法直接整合至鏈結和應用程式。

retriever = vector_store.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 3,
        "fetch_k": 10,
        "lambda_mult": 0.3,
    }
)

retrieved_docs = retriever.invoke(user_query)
print(f"\nRetriever MMR Results for '{user_query}':")
for doc in retrieved_docs:
    print(f"  - {doc.metadata['name']}: {doc.page_content}")

後續步驟