Supabase (PostgreSQL)
Supabase 是一個開放原始碼的
Firebase
替代方案。Supabase
建構於PostgreSQL
之上,提供強大的SQL
查詢功能,並能與現有的工具和框架輕鬆整合。
PostgreSQL,也稱為
Postgres
,是一個免費且開放原始碼的關聯式資料庫管理系統 (RDBMS),強調可擴展性和SQL
相容性。Supabase 提供了一個開放原始碼工具組,用於使用 Postgres 和 pgvector 開發 AI 應用程式。使用 Supabase 用戶端函式庫來大規模儲存、索引和查詢您的向量嵌入。
在本筆記本中,我們將示範 SelfQueryRetriever
包裝在 Supabase
向量儲存庫周圍的用法。
具體來說,我們將:
- 建立 Supabase 資料庫
- 啟用
pgvector
擴充功能 - 建立
documents
表格和match_documents
函數,這些將被SupabaseVectorStore
使用 - 將範例文件載入到向量儲存庫(資料庫表格)中
- 建立並測試自我查詢檢索器
設定 Supabase 資料庫
- 前往 https://database.new 以佈建您的 Supabase 資料庫。
- 在 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
由於我們正在使用 SupabaseVectorStore
和 OpenAIEmbeddings
,我們必須載入它們的 API 金鑰。
-
若要找到您的
SUPABASE_URL
和SUPABASE_SERVICE_KEY
,請前往您的 Supabase 專案的 API 設定。SUPABASE_URL
對應於專案 URLSUPABASE_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'})]