JavaScriptLangChain.jsNode.jsSaaS / FaaSSupabase

LangChainのベクターストアとしてSupabaseを試してみた

Supabaseは、pgvectorを使用しているようで、他のPostgreSQL版(Amazon RDSなど)と組み合わせて使用することも可能です。Supabaseのプロジェクトを作成し、データベースを生成するSQLを実行します。また、Supabaseを使用してデータを挿入し、類似検索を行うAPIを作成することもできます。データの確認はTable EditorやSupabase SDKから行えます。ただし、RAGを使用する場合は負荷や影響を考慮する必要があります。

広告ここから
広告ここまで

ベクターストアとしてsupabaseを試してました。内部的にはpgvectorを使っている様子なので、Amazon RDSなどのPostgreSQL版をOSS版Supabaseと組み合わせて使うとかもできるかもしれません。

SupabaseのDBを用意する

まずはdatabase.newからSupabaseのプロジェクトを新規作成します。

データベースを生成するSQLを実行します。DocsのSQLをSupabaseのダッシュボードで実行しましょう。

-- Enable the pgvector extension to work with embedding vectors
create extension vector;

-- Create a table to store your documents
create table documents (
  id bigserial 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),
  match_count int DEFAULT null,
  filter jsonb DEFAULT '{}'
) returns table (
  id bigint,
  content text,
  metadata jsonb,
  embedding jsonb,
  similarity float
)
language plpgsql
as $$
#variable_conflict use_column
begin
  return query
  select
    id,
    content,
    metadata,
    (embedding::text)::jsonb as embedding,
    1 - (documents.embedding <=> query_embedding) as similarity
  from documents
  where metadata @> filter
  order by documents.embedding <=> query_embedding
  limit match_count;
end;
$$;

SQL Editorがあるので、GUIで実行できるのが便利ですね。

[Results]に成功メッセージが出ていれば、作成成功です。

Table Editorなどでリソースの作成が成功していることが確認できます。

HonoでAPIを作成する

ここからの実装は、Hono上で行います。ライブラリを追加でインストールしておきましょう。

npm install -S @supabase/supabase-js langchain

環境変数を3つ.dev.varsに設定します。

SUPABASE_PRIVATE_KEY=xxxx
SUPABASE_URL=https://xxxxx.supabase.co
OPENAI_API_KEY=sk-xxx

TypeScriptでのBindingsはこんな感じです。

import { Hono } from "hono";

export const supabaseApp = new Hono<{
    Bindings: {
        SUPABASE_PRIVATE_KEY: string;
        SUPABASE_URL: string;
        OPENAI_API_KEY: string;
    }
}>();

あとはインデックスへの投入を検索の処理を備えたAPIを用意するだけです。SupabaseVectorStore.fromTextsにデータを渡すと、EmbeddingされたデータがSupabaseにInsertされます。データに対する検索は、similaritySearchで行います。

import { SupabaseVectorStore } from "langchain/vectorstores/supabase";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { createClient } from "@supabase/supabase-js";

supabaseApp.get('ask', async c => {
    const privateKey = c.env.SUPABASE_PRIVATE_KEY;
    const url = c.env.SUPABASE_URL;
    const client = createClient(url, privateKey);
  
    const vectorStore = await SupabaseVectorStore.fromTexts(
      ["Hello world", "Bye bye", "What's this?"],
      [{ id: 2 }, { id: 1 }, { id: 3 }],
      new OpenAIEmbeddings({
        openAIApiKey: c.env.OPENAI_API_KEY
      }),
      {
        client,
        tableName: "documents",
        queryName: "match_documents",
      }
    );
  
    const resultOne = await vectorStore.similaritySearch("Hello world", 1);
  
    console.log(resultOne);
    return c.json(resultOne)
})

実行すると、一致または類似するデータが返ってきました。similaritySearchの第二引数で件数を指定できます。

% curl http://127.0.0.1:8787/supabase/ask
[{"pageContent":"Hello world","metadata":{"id":2}}]

テーブルデータをSupabaseのTable Editorでみてみる

投入されたデータはSupabaseのGUIからも確認できます。

Supabase SDKで直接取得してみる

また、SupabaseのDBに投入されたデータなので、Supabase SDKからも取得できます。

supabaseApp.get('ask', async c => {
    const privateKey = c.env.SUPABASE_PRIVATE_KEY;
    const url = c.env.SUPABASE_URL;
    const client = createClient(url, privateKey);
    const data = await client.from('documents').select("id, content, metadata")
    return c.json(data)
});

通常のSQLを実行した形ですが、LangChainから投入したデータが見ることがわかります。

{
  "error": null,
  "data": [
    {
      "id": 1,
      "content": "Hello world",
      "metadata": {
        "id": 2
      }
    },
    {
      "id": 2,
      "content": "Bye bye",
      "metadata": {
        "id": 1
      }
    },
    {
      "id": 3,
      "content": "What's this?",
      "metadata": {
        "id": 3
      }
    }
  ],
  "count": null,
  "status": 200,
  "statusText": "OK"
}

もしEmbeddingしたベクトルデータが欲しい場合は、selectembeddingを渡しましょう。

await client.from('documents').select("embedding")

ただしOpenAIやClaudeを使う場合、Embeddingした配列の長さが1,000を超えるため、必要になるまでは取らない方がデバッグがしやすいかなと思います。

[   {
      "embedding": "[0.0055157384,-0.009889425,0.016879791,-0.008615596,-0.030847976,0.00419171,-0.0043925107,-0.0003137508,-0.012154705,-0.02002985,0.017494744,-0.00610559,-0.0058389017,-0.011771929,0.014558036,0.006908792,0.03295638,0.0026417815,-0.0022637118,-0.021309953,-0.008866597,0.004263873,-0.003231633,-0.011577403,0.0031469204,0.024736112,0.017457092,-0.012913981,0.0033163456,-0.0123555055,0.03631979,-0.011458178,-2.2170905e-05,-0.012964182,-0.012757107,-0.009412523,0.0013938379,-0.04362391,0.0060240147,-0.0054341634,0.00015569883,0.008176345,-0.0013389314,-0.0026715877,-0.014357235,-0.0011083246,0.008948172,-0.032052778,0.0054843635,0.012556306,0.009431348,-0.00011883311,-0.026279764,-0.008609321,-0.022539856,0.004414473,-0.017996745,0.010924802,-0.015536939,0.008050845,-0.004389373,0.005569076,-0.023531308,0.013114782,0.016503291,0.001931136,-0.0056820265,-0.010027475,-0.027308868,-0.009832949,0.012719456,0.037800692,0.007680619,-0.009224272,0.014394886,-0.016992742,-0.045682114,-0.02068245,0.01580049,-0.0047721495,0.02813717,-0.017682994,-0.011301303,0.008
...

感想

実態がpgvectorらしいので、当然ではありますが、RDBMSでベクトルデータを扱えるのはいろいろと取り回しがききそうな予感がします。ただしRAGはAPI呼び出しが頻繁になる可能性がありますので、データベースへの負荷や通常のSQL呼び出し部分への影響などは考えた方がいい・・・のかもしれません。

参考記事

https://js.langchain.com/docs/integrations/vectorstores/supabase

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts