how to set bring embedding-search into your app in 15 minutes
Aug 27, 2024
If you just want an embedding service go here
Hi Iโm nikolay. Iโm a pro developer working on my own ideas and I have til midnight tonight (2024/09/27) to build a clone of sage by buildspace.
Buildspace is closing down. I went thru s4 last year. I didnโt win, go viral or have any moderate local success. What I did gain is undying resolve to buidl โข๏ธ๏ธ . Iโve met cool ppl on sage, I thought we could keep it going.
why not?
Ever wanted to search over some documents like its 2023? Start the clock, letโs set up an embedding service.
This setup is optimised for speed to production and simplicity. This means:
First we need text to embed. I will use user bios from a service called sage-clone
. Any text will work: valentineโs day cards, pokemon card descriptions, your poetry collection; go wild!
For my example I want to:
Supabase have great support for embeddings and a lot of case-studies to learn from. Letโs set it up.
Letโs set up supabase.
supabase init
supabase start # start the local workspace (might take a minute)
supabase link --project-ref $YOUR_PROJECT_REF
supabase functions new embed && supabase functions new query
Check the embeddings work by replacing the embed/index.ts
with
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
= new Supabase.ai.Session("gte-small");
const session
.serve(async (req) => {
Deno// Extract input string from JSON body
= await req.json();
const { input }
// Generate the embedding from the user input
= await session.run(input, {
const embedding : true,
mean_pool: true,
normalize;
})
// Return the embedding
new Response(
return JSON.stringify({ embedding }),
: { "Content-Type": "application/json" } },
{ headers;
); })
Run supabase functions serve
and curl the endpoint!
Create the initial migration with
supabase migrations new init
This will set up the database for us to store and query our embeddings. This is the real meat and potatoes of this operation. Read the comments for the explanation of each step.
-- Step 1: Create a new schema for bios
CREATE SCHEMA IF NOT EXISTS bios;
grant usage on schema bios to postgres, service_role;
-- Step 2: Enable the pgvector extension for vector operations
CREATE EXTENSION IF NOT EXISTS vector;
-- Step 3: Create the bio_sections table in the new schema
CREATE TABLE bios.bio_sections (
id BIGSERIAL PRIMARY KEY,
NOT NULL UNIQUE,
user_id UUID NOT NULL,
section_type TEXT NOT NULL,
content TEXT 384),
embedding VECTOR(INT,
token_count DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMPTZ
);alter table bios.bio_sections enable row level security;
-- Step 4: Create function for matching similar bio sections
CREATE OR REPLACE FUNCTION bios.match_similar_bio_sections(
384),
query_embedding VECTOR(FLOAT,
similarity_threshold INT
max_results
)TABLE (
RETURNS
user_id UUID,
section_type TEXT,
content TEXT,FLOAT
similarity
)
LANGUAGE plpgsqlAS $$
BEGIN
RETURN QUERY
SELECT
bs.user_id,
bs.section_type,
bs.content,1 - (bs.embedding <=> query_embedding) AS similarity
FROM
bios.bio_sections bsWHERE
1 - (bs.embedding <=> query_embedding) > similarity_threshold
ORDER BY
<=> query_embedding
bs.embedding LIMIT max_results;
END;
$$;
-- Step 5: Set privileges for the bios schema
ALTER DEFAULT PRIVILEGES IN SCHEMA bios
GRANT ALL ON TABLES TO postgres, service_role;
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA bios
TO postgres, service_role;
GRANT USAGE, SELECT
ON ALL SEQUENCES IN SCHEMA bios
TO postgres, service_role;
Bravely push the migration straight to prod! ๐
NB: Donโt forget to make the bios
schema visible in API in Supabase project settings in the browser.
The functions are quite simple. The shell is identical:
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
// set up supabase, Supabase runtime sets the Env vars.
= Deno.env.get("SUPABASE_URL");
const supabaseUrl = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
const supabaseServiceRoleKey = createClient(supabaseUrl!, supabaseServiceRoleKey!, {
const supabase : {
db: "bios", // point at schema we're working with
schema,
};
})
// Supabase comes bundled with an embedding model already.
= new Supabase.ai.Session("gte-small");
const session
.serve(async (req) => {
Deno
try {// YOUR FUNCTION GOES HERE
catch (error) {
} new Response(
return JSON.stringify({ error: error.message }),
: { "Content-Type": "application/json" }, status: 500 },
{ headers;
)
}; })
The function body of functions/add_embedding/index.ts
and functions/query_embedding/index.ts
are again very simple.
First, embed the bio section and upsert.
, sectionType, content } = await req.json();
const { userId
// Generate the embedding from the user input
= await session.run(content, {
const embedding : true,
mean_pool: true,
normalize;
})
// upsert the content into the bios_sections table
, error } = await supabase
const { data.from("bio_sections")
.upsert(
{: userId,
user_id: sectionType,
section_type,
content,
embedding,
}
{: ["user_id"],
onConflict,
};
)
if (error) throw error;
new Response(JSON.stringify(data), {
return : { "Content-Type": "application/json" },
headers: 200,
status; })
Query the sections:
= await req.json();
const { query }
if (!query) {
new Error("Missing query parameter");
throw
}
// Generate the embedding from the user input
= await session.run(query, {
const embedding : true,
mean_pool: true,
normalize;
})
: bioSections } = await supabase.rpc(
const { data"match_similar_bio_sections",
{: embedding, // Pass the embedding you want to compare
query_embedding: 0.3, // Choose an appropriate threshold for your data
similarity_threshold: 10, // Choose the number of matches
max_results,
};
)
new Response(JSON.stringify(bioSections), {
return : { "Content-Type": "application/json" },
headers: 200,
status; })
After running supabase functions deploy
our embedding service is live. We can access it over HTTP with a Supabase API Key.
curl --request POST 'http://localhost:54321/functions/v1/add_embedding' \
--header 'Authorization: Bearer ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{ "userId": "f918dbb0-fd13-470c-98e9-3d92dfc57ce3", "sectionType": "swag", "content": "i'm a little teapot short and stout" }'
๐ Congratulations! Youโve set up your psql embedding pipeline in 15 minutes! ๐