Bigtable で LangChain を使用して周辺関連性最大化検索を行う

このページでは、Bigtable の LangChain 用 BigtableVectorStore インテグレーションと Vertex AI をエンベディング サービスとして使用して、最大限界関連性(MMR)検索を行う方法について説明します。

MMR は、情報検索で使用される検索手法で、クエリに関連性があり、多様な結果のセットを返し、冗長性を回避します。標準のベクトル類似性検索(k 近傍法を使用する検索など)では、類似したアイテムが多数返される可能性がありますが、MMR では、より多様な上位結果のセットが提供されます。これは、ベクトル ストアに重複する可能性のあるデータがある場合に便利です。

たとえば、e コマース アプリケーションでユーザーが「赤いトマト」を検索すると、ベクトル類似性検索は同じ種類の新鮮な赤いトマトの複数のリスティングを返すことがあります。MMR 検索では、「新鮮な赤いトマト」、「赤いトマトの缶詰」、「有機チェリートマト」、「トマトサラダのレシピ」など、より多様な結果を返すことを目指します。

このページを読む前に、次のコンセプトを理解しておくことが重要です。

  • 関連性: ドキュメントがクエリにどの程度一致するかを測定します。
  • 多様性: ドキュメントが結果セットで既に選択されているドキュメントとどの程度異なるかを測定します。
  • ラムダ乗数: 関連性と多様性のバランスを取る 0 ~ 1 の間の係数。値が 1 に近いほど関連性が重視され、値が 0 に近いほど多様性が重視されます。

LangChain の BigtableVectorStore クラスは、MMR を再ランキング アルゴリズムとして実装します。このアルゴリズムは、まずクエリに関連するドキュメントの大きなセットを取得し、関連性と多様性のバランスが取れるようにクエリに一致するドキュメントを選択します。

始める前に

このガイドでは、エンベディング サービスとして Vertex AI を使用します。プロジェクトで Vertex AI API が有効になっていることを確認します。

Vertex AI API を有効にする

必要なロール

LangChain で Bigtable を使用するには、次の IAM ロールが必要です。

  • Bigtable インスタンスに対する Bigtable ユーザー(roles/bigtable.user)。
  • テーブルを初期化する場合は、Bigtable 管理者(roles/bigtable.admin)ロールも必要です。

環境の設定

  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 テーブルの識別子を渡して、ストア インスタンスを作成します。

# 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.")

ベクトルストアにデータを入力する

このガイドでは、ユーザーが赤いトマトに関連するアイテムを検索したいという架空の e コマース サービスのサンプル シナリオを使用します。まず、トマト関連の商品と説明をベクトル ストアに追加する必要があります。

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(int): 検索結果の最終的な数。
  • 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}")

次のステップ