跳到主要內容
Open In ColabOpen on GitHub

Supabase (PostgreSQL)

Supabase 是一個開放原始碼的 Firebase 替代方案。Supabase 建構於 PostgreSQL 之上,提供強大的 SQL 查詢功能,並能與現有的工具和框架輕鬆整合。

PostgreSQL,也稱為 Postgres,是一個免費且開放原始碼的關聯式資料庫管理系統 (RDBMS),強調可擴展性和 SQL 相容性。

Supabase 提供了一個開放原始碼工具組,用於使用 Postgres 和 pgvector 開發 AI 應用程式。使用 Supabase 用戶端函式庫來大規模儲存、索引和查詢您的向量嵌入。

在本筆記本中,我們將示範 SelfQueryRetriever 包裝在 Supabase 向量儲存庫周圍的用法。

具體來說,我們將:

  1. 建立 Supabase 資料庫
  2. 啟用 pgvector 擴充功能
  3. 建立 documents 表格和 match_documents 函數,這些將被 SupabaseVectorStore 使用
  4. 將範例文件載入到向量儲存庫(資料庫表格)中
  5. 建立並測試自我查詢檢索器

設定 Supabase 資料庫

  1. 前往 https://database.new 以佈建您的 Supabase 資料庫。
  2. 在 Studio 中,跳到 SQL 編輯器 並執行以下腳本,以啟用 pgvector 並將您的資料庫設定為向量儲存庫
    -- Enable the pgvector extension to work with embedding vectors
    create extension if not exists vector;

    -- Create a table to store your documents
    create table
    documents (
    id uuid primary key,
    content text, -- corresponds to Document.pageContent
    metadata jsonb, -- corresponds to Document.metadata
    embedding vector (1536) -- 1536 works for OpenAI embeddings, change if needed
    );

    -- Create a function to search for documents
    create function match_documents (
    query_embedding vector (1536),
    filter jsonb default '{}'
    ) returns table (
    id uuid,
    content text,
    metadata jsonb,
    similarity float
    ) language plpgsql as $$
    #variable_conflict use_column
    begin
    return query
    select
    id,
    content,
    metadata,
    1 - (documents.embedding <=> query_embedding) as similarity
    from documents
    where metadata @> filter
    order by documents.embedding <=> query_embedding;
    end;
    $$;

建立 Supabase 向量儲存庫

接下來,我們需要建立一個 Supabase 向量儲存庫,並用一些資料來填充它。我們建立了一個小型示範文件集,其中包含電影摘要。

請務必安裝最新版本的 langchain 並支援 openai

%pip install --upgrade --quiet  langchain langchain-openai tiktoken

自我查詢檢索器需要您安裝 lark

%pip install --upgrade --quiet  lark

我們也需要 supabase 套件

%pip install --upgrade --quiet  supabase

由於我們正在使用 SupabaseVectorStoreOpenAIEmbeddings,我們必須載入它們的 API 金鑰。

  • 若要找到您的 SUPABASE_URLSUPABASE_SERVICE_KEY,請前往您的 Supabase 專案的 API 設定

    • SUPABASE_URL 對應於專案 URL
    • SUPABASE_SERVICE_KEY 對應於 service_role API 金鑰
  • 若要取得您的 OPENAI_API_KEY,請導航至您的 OpenAI 帳戶上的 API 金鑰 並建立新的密鑰。

import getpass
import os

if "SUPABASE_URL" not in os.environ:
os.environ["SUPABASE_URL"] = getpass.getpass("Supabase URL:")
if "SUPABASE_SERVICE_KEY" not in os.environ:
os.environ["SUPABASE_SERVICE_KEY"] = getpass.getpass("Supabase Service Key:")
if "OPENAI_API_KEY" not in os.environ:
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

選用: 如果您將 Supabase 和 OpenAI API 金鑰儲存在 .env 檔案中,您可以使用 dotenv 載入它們。

%pip install --upgrade --quiet  python-dotenv
from dotenv import load_dotenv

load_dotenv()

首先,我們將建立一個 Supabase 用戶端並實例化一個 OpenAI 嵌入類別。

import os

from langchain_community.vectorstores import SupabaseVectorStore
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from supabase.client import Client, create_client

supabase_url = os.environ.get("SUPABASE_URL")
supabase_key = os.environ.get("SUPABASE_SERVICE_KEY")
supabase: Client = create_client(supabase_url, supabase_key)

embeddings = OpenAIEmbeddings()

接下來,讓我們建立文件。

docs = [
Document(
page_content="A bunch of scientists bring back dinosaurs and mayhem breaks loose",
metadata={"year": 1993, "rating": 7.7, "genre": "science fiction"},
),
Document(
page_content="Leo DiCaprio gets lost in a dream within a dream within a dream within a ...",
metadata={"year": 2010, "director": "Christopher Nolan", "rating": 8.2},
),
Document(
page_content="A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea",
metadata={"year": 2006, "director": "Satoshi Kon", "rating": 8.6},
),
Document(
page_content="A bunch of normal-sized women are supremely wholesome and some men pine after them",
metadata={"year": 2019, "director": "Greta Gerwig", "rating": 8.3},
),
Document(
page_content="Toys come alive and have a blast doing so",
metadata={"year": 1995, "genre": "animated"},
),
Document(
page_content="Three men walk into the Zone, three men walk out of the Zone",
metadata={
"year": 1979,
"director": "Andrei Tarkovsky",
"genre": "science fiction",
"rating": 9.9,
},
),
]

vectorstore = SupabaseVectorStore.from_documents(
docs,
embeddings,
client=supabase,
table_name="documents",
query_name="match_documents",
)

建立自我查詢檢索器

現在我們可以實例化我們的檢索器。為此,我們需要預先提供一些關於我們的文件支援的中繼資料欄位以及文件內容的簡短描述的資訊。

from langchain.chains.query_constructor.schema import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain_openai import OpenAI

metadata_field_info = [
AttributeInfo(
name="genre",
description="The genre of the movie",
type="string or list[string]",
),
AttributeInfo(
name="year",
description="The year the movie was released",
type="integer",
),
AttributeInfo(
name="director",
description="The name of the movie director",
type="string",
),
AttributeInfo(
name="rating", description="A 1-10 rating for the movie", type="float"
),
]
document_content_description = "Brief summary of a movie"
llm = OpenAI(temperature=0)
retriever = SelfQueryRetriever.from_llm(
llm, vectorstore, document_content_description, metadata_field_info, verbose=True
)

測試一下

現在我們可以嘗試實際使用我們的檢索器了!

# This example only specifies a relevant query
retriever.invoke("What are some movies about dinosaurs")
query='dinosaur' filter=None limit=None
[Document(page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose', metadata={'year': 1993, 'genre': 'science fiction', 'rating': 7.7}),
Document(page_content='Toys come alive and have a blast doing so', metadata={'year': 1995, 'genre': 'animated'}),
Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'year': 1979, 'genre': 'science fiction', 'rating': 9.9, 'director': 'Andrei Tarkovsky'}),
Document(page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea', metadata={'year': 2006, 'rating': 8.6, 'director': 'Satoshi Kon'})]
# This example only specifies a filter
retriever.invoke("I want to watch a movie rated higher than 8.5")
query=' ' filter=Comparison(comparator=<Comparator.GT: 'gt'>, attribute='rating', value=8.5) limit=None
[Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'year': 1979, 'genre': 'science fiction', 'rating': 9.9, 'director': 'Andrei Tarkovsky'}),
Document(page_content='A psychologist / detective gets lost in a series of dreams within dreams within dreams and Inception reused the idea', metadata={'year': 2006, 'rating': 8.6, 'director': 'Satoshi Kon'})]
# This example specifies a query and a filter
retriever.invoke("Has Greta Gerwig directed any movies about women?")
query='women' filter=Comparison(comparator=<Comparator.EQ: 'eq'>, attribute='director', value='Greta Gerwig') limit=None
[Document(page_content='A bunch of normal-sized women are supremely wholesome and some men pine after them', metadata={'year': 2019, 'rating': 8.3, 'director': 'Greta Gerwig'})]
# This example specifies a composite filter
retriever.invoke("What's a highly rated (above 8.5) science fiction film?")
query=' ' filter=Operation(operator=<Operator.AND: 'and'>, arguments=[Comparison(comparator=<Comparator.GTE: 'gte'>, attribute='rating', value=8.5), Comparison(comparator=<Comparator.EQ: 'eq'>, attribute='genre', value='science fiction')]) limit=None
[Document(page_content='Three men walk into the Zone, three men walk out of the Zone', metadata={'year': 1979, 'genre': 'science fiction', 'rating': 9.9, 'director': 'Andrei Tarkovsky'})]
# This example specifies a query and composite filter
retriever.invoke(
"What's a movie after 1990 but before (or on) 2005 that's all about toys, and preferably is animated"
)
query='toys' filter=Operation(operator=<Operator.AND: 'and'>, arguments=[Comparison(comparator=<Comparator.GT: 'gt'>, attribute='year', value=1990), Comparison(comparator=<Comparator.LTE: 'lte'>, attribute='year', value=2005), Comparison(comparator=<Comparator.LIKE: 'like'>, attribute='genre', value='animated')]) limit=None
[Document(page_content='Toys come alive and have a blast doing so', metadata={'year': 1995, 'genre': 'animated'})]

篩選 k

我們也可以使用自我查詢檢索器來指定 k:要提取的文件數量。

我們可以透過將 enable_limit=True 傳遞給建構函式來做到這一點。

retriever = SelfQueryRetriever.from_llm(
llm,
vectorstore,
document_content_description,
metadata_field_info,
enable_limit=True,
verbose=True,
)
# This example only specifies a relevant query
retriever.invoke("what are two movies about dinosaurs")
query='dinosaur' filter=None limit=2
[Document(page_content='A bunch of scientists bring back dinosaurs and mayhem breaks loose', metadata={'year': 1993, 'genre': 'science fiction', 'rating': 7.7}),
Document(page_content='Toys come alive and have a blast doing so', metadata={'year': 1995, 'genre': 'animated'})]

此頁面是否對您有幫助?