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

このページでは、Bigtable の LangChain 用 BigtableVectorStore 統合と Gemini Enterprise Agent Platform をエンベディング サービスとして使用して、周辺関連性最大化(MMR)検索を行う方法について説明します。

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

たとえば、e コマース アプリケーションで、ユーザーが「赤いトマト」を検索した場合、ベクトル類似性検索では、同じ種類の新鮮な赤いトマトのリストが複数返される可能性があります。MMR 検索では、「新鮮な赤いトマト」、「赤いトマトの缶詰」、「オーガニック ミニトマト」、「トマトサラダのレシピ」など、より多様なセットを返すことを目指します。

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

  • 関連性: ドキュメントがクエリにどの程度一致しているかを示す指標。
  • 多様性: ドキュメントが結果セットで 選択済みのドキュメントとどの程度異なるかを示す指標。
  • ラムダ乗数: 関連性と多様性の バランスを取る 0 ~ 1 の係数。1 に近い値は関連性を優先し、0 に近い値は多様性を優先します。

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

始める前に

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

Agent Platform 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 モデルによって生成されたエンベディングを指定する必要があります。このガイドでは、Agent Platform のテキスト エンベディング gemini-embedding-001 モデルを使用します。


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-001", 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(float): 多様性の調整パラメータ。多様性を最大にする場合は 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}")

次のステップ