From 2a4c525cd2ffa2b8ce7d1240bde1a744f0c0236c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thor=20=E9=9B=B7=E7=A5=9E=20Schaeff?= <5748289+thorwebdev@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:36:19 +0800 Subject: [PATCH 01/18] docs: ai in ef docs guide (#22788) * wip: add semantic search example * chore: complete ef ai guide. * chore: update ai docs. --------- Co-authored-by: Lakshan Perera --- .../NavigationMenu.constants.ts | 4 + .../content/guides/ai/semantic-search.mdx | 2 +- .../functions/examples/semantic-search.mdx | 129 ++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/guides/functions/examples/semantic-search.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 62ed67d0656..4e1daa362e9 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1187,6 +1187,10 @@ export const functions: NavMenuConstant = { name: 'Generating OG images ', url: '/guides/functions/examples/og-image', }, + { + name: 'Semantic AI Search', + url: '/guides/functions/examples/semantic-search', + }, { name: 'CAPTCHA support with Cloudflare Turnstile', url: '/guides/functions/examples/cloudflare-turnstile', diff --git a/apps/docs/content/guides/ai/semantic-search.mdx b/apps/docs/content/guides/ai/semantic-search.mdx index a9a219f7213..44744441840 100644 --- a/apps/docs/content/guides/ai/semantic-search.mdx +++ b/apps/docs/content/guides/ai/semantic-search.mdx @@ -26,7 +26,7 @@ Embeddings are generated using a language model, and embeddings are compared to ## Embedding models -There are many embedding models available today. Some can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings). +There are many embedding models available today. Supabase Edge Functions has [built in support](/docs/guides/functions/examples/semantic-search) for the `gte-small` model. Others can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings). It's crucial to remember that when using embedding models with semantic search, you must use the same model for all embedding comparisons. Comparing embeddings created by different models will yield meaningless results. diff --git a/apps/docs/content/guides/functions/examples/semantic-search.mdx b/apps/docs/content/guides/functions/examples/semantic-search.mdx new file mode 100644 index 00000000000..b42077dd395 --- /dev/null +++ b/apps/docs/content/guides/functions/examples/semantic-search.mdx @@ -0,0 +1,129 @@ +--- +id: 'function-ai-models' +title: 'Semantic Search' +description: 'Semantic Search with pgvector and Supabase Edge Functions' +subtitle: 'Semantic Search with pgvector and Supabase Edge Functions' +tocVideo: 'w4Rr_1whU-U' +--- + +[Semantic search](/docs/guides/ai/semantic-search) interprets the meaning behind user queries rather than exact [keywords](/docs/guides/ai/keyword-search). It uses machine learning to capture the intent and context behind the query, handling language nuances like synonyms, phrasing variations, and word relationships. + +Since Supabase Edge Runtime [v1.36.0](https://github.com/supabase/edge-runtime/releases/tag/v1.36.0) you can run the [`gte-small` model](https://huggingface.co/Supabase/gte-small) natively within Supabase Edge Functions without any external dependencies! This allows you to easily generate text embeddings without calling any external APIs! + +In this tutorial you're implementing three parts: + +1. A [`generate-embedding`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts) database webhook edge function which generates embeddings when a content row is added (or updated) in the [`public.embeddings`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql) table. +2. A [`query_embeddings` Postgres function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql) which allows us to perform similarity search from an egde function via [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js). +3. A [`search` edge function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/search/index.ts) which generates the embedding for the search term, performs the similarity search via RPC function call, and returns the result. + +You can find the complete example code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions) + +### Create the database table and webhook + +Given the [following table definition](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql): + +```sql +create extension if not exists vector with schema extensions; + +create table embeddings ( + id bigint primary key generated always as identity, + content text not null, + embedding vector (384) +); +alter table embeddings enable row level security; + +create index on embeddings using hnsw (embedding vector_ip_ops); +``` + +You can deploy the [following edge function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts) as a [database webhook](/docs/guides/database/webhooks) to generate the embeddings for any text content inserted into the table: + +```tsx +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req) => { + const payload: WebhookPayload = await req.json() + const { content, id } = payload.record + + // Generate embedding. + const embedding = await model.run(content, { + mean_pool: true, + normalize: true, + }) + + // Store in database. + const { error } = await supabase + .from('embeddings') + .update({ embedding: JSON.stringify(embedding) }) + .eq('id', id) + if (error) console.warn(error.message) + + return new Response('ok') +}) +``` + +## Create a Postgres Function and RPC + +With the embeddings now stored in your Postgres database table, you can query them from Supabase Edge Functions by utilizing [Remote Procedure Calls (RPC)](/docs/guides/database/functions?language=js). + +Given the [following Postgres Function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql): + +```sql +-- Matches document sections using vector similarity search on embeddings +-- +-- Returns a setof embeddings so that we can use PostgREST resource embeddings (joins with other tables) +-- Additional filtering like limits can be chained to this function call +create or replace function query_embeddings(embedding vector(384), match_threshold float) +returns setof embeddings +language plpgsql +as $$ +begin + return query + select * + from embeddings + + -- The inner product is negative, so we negate match_threshold + where embeddings.embedding <#> embedding < -match_threshold + + -- Our embeddings are normalized to length 1, so cosine similarity + -- and inner product will produce the same query results. + -- Using inner product which can be computed faster. + -- + -- For the different distance functions, see https://github.com/pgvector/pgvector + order by embeddings.embedding <#> embedding; +end; +$$; +``` + +## Query vectors in Supabase Edge Functions + +You can use `supabase-js` to first generate the embedding for the search term and then invoke the Postgres function to find the relevant results from your stored embeddings, right from your [Supabase Edge Function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/search/index.ts): + +```tsx +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req) => { + const { search } = await req.json() + if (!search) return new Response('Please provide a search param!') + // Generate embedding for search term. + const embedding = await model.run(search, { + mean_pool: true, + normalize: true, + }) + + // Query embeddings. + const { data: result, error } = await supabase + .rpc('query_embeddings', { + embedding, + match_threshold: 0.8, + }) + .select('content') + .limit(3) + if (error) { + return Response.json(error) + } + + return Response.json({ search, result }) +}) +``` + +That's it, you now have AI powered semantic search set up without any external dependencies! Just you, pgvector, and Supabase Edge Functions! From 05b1bd97d102fc7a2f2e6d29914d9df63d87bb84 Mon Sep 17 00:00:00 2001 From: Inian Date: Tue, 16 Apr 2024 21:47:21 +0800 Subject: [PATCH 02/18] add day2 docs (#22787) * wip * inference * inference * inference * Update ai-models.mdx --------- Co-authored-by: Lakshan Perera --- .../NavigationMenu.constants.ts | 4 + .../content/guides/functions/ai-models.mdx | 171 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 apps/docs/content/guides/functions/ai-models.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 4e1daa362e9..bf1e5bda20e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1139,6 +1139,10 @@ export const functions: NavMenuConstant = { name: 'Handling Routing in Functions', url: '/guides/functions/routing', }, + { + name: 'Running AI Models', + url: '/guides/functions/ai-models', + }, { name: 'Regional invocations', url: '/guides/functions/regional-invocation', diff --git a/apps/docs/content/guides/functions/ai-models.mdx b/apps/docs/content/guides/functions/ai-models.mdx new file mode 100644 index 00000000000..e6e593a5f0a --- /dev/null +++ b/apps/docs/content/guides/functions/ai-models.mdx @@ -0,0 +1,171 @@ +--- +id: 'function-ai-models' +title: 'Running AI Models' +description: 'How to run AI models in Edge Functions.' +subtitle: 'How to run AI models in Edge Functions.' +--- + +[Supabase Edge Runtime](https://github.com/supabase/edge-runtime) has a built-in API for running AI models. You can use this API to generate embeddings, build conversational workflows, and do other AI related tasks in your Edge Functions. + +## Setup + +There are no external dependencies or packages to install to enable the API. + +You can create a new inference session by doing: + +```ts +const model = new Supabase.ai.Session('model-name') +``` + + + +To get type hints and checks for the API we recommend adding this triple-slash directive to the start of the Edge Function code: `/// ` + + + +## Running a model inference + +Once the session is instantiated, you can call it with inputs to perform inferences. Depending on the model you run, you may need to provide different options (discussed below). + +```ts +const output = await model.run(input, options) +``` + +## How to generate text embeddings + +Now let's see how to write an Edge Function using the `Supabase.ai` API to generate text embeddings. Currently, `Supabase.ai` API only supports the [gte-small](https://huggingface.co/Supabase/gte-small) model. + + + +`gte-small` model exclusively caters to English texts, and any lengthy texts will be truncated to a maximum of 512 tokens. While you can provide inputs longer than 512 tokens, truncation may affect the accuracy. + + + +```ts +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req: Request) => { + const params = new URL(req.url).searchParams + const input = params.get('input') + const output = await model.run(input, { mean_pool: true, normalize: true }) + return new Response(JSON.stringify(output), { + headers: { + 'Content-Type': 'application/json', + Connection: 'keep-alive', + }, + }) +}) +``` + +## Using larger models + +Inference via larger models is supported via [Ollama](https://ollama.com/). In the first iteration, the `llama2` and `mistral` models are supported and you would need to manage the Ollama server yourself. + + + +### Running locally + +1. Install Ollama and pull the Mistral model + +``` +ollama pull mistral +``` + +2. Run the Ollama server locally + +``` +ollama serve +``` + +3. Set a function secret called AI_INFERENCE_API_HOST to point to the Ollama server + +``` +echo "AI_INFERENCE_API_HOST=http://host.docker.internal:11434/api/generate" >> supabase/functions/.env +``` + +4. Create a new function with the following code + +``` +supabase functions new ollama-test +``` + +```ts +/// +const session = new Supabase.ai.Session('mistral') + +Deno.serve(async (req: Request) => { + const params = new URL(req.url).searchParams + const prompt = params.get('prompt') ?? '' + + // Get the output as a stream + const output = await session.run(prompt, { stream: true }) + + const headers = new Headers({ + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + }) + + // Create a stream + const stream = new ReadableStream({ + async start(controller) { + const encoder = new TextEncoder() + + try { + for await (const chunk of output) { + controller.enqueue(encoder.encode(chunk.response ?? '')) + } + } catch (err) { + console.error('Stream error:', err) + } finally { + controller.close() + } + }, + }) + + // Return the stream to the user + return new Response(stream, { + headers, + }) +}) +``` + +5. Execute the function + +``` +curl --get "http://localhost:54321/functions/v1/ollama-test" \ +--data-urlencode "prompt=write a short rap song about Supabase, the Postgres Developer platform, as sung by Nicki Minaj" \ +-H "Authorization: $ANON_KEY" +``` + +### Deploying to production + +Once the function is working locally, it's time to deploy to production. + +1. Deploy a Ollama server and set a function secret called `AI_INFERENCE_API_HOST` to point to the deployed Ollama server + +``` +supabase secrets set AI_INFERENCE_API_HOST=https://path-to-your-ollama-server/ +``` + +2. Deploy the Supabase function + +``` +supabase functions deploy ollama-test +``` + +3. Execute the function + +``` +curl --get "https://project-ref.supabase.co/functions/v1/ollama-test" \ +--data-urlencode "prompt=write a short rap song about Supabase, the Postgres Developer platform, as sung by Nicki Minaj" \ +-H "Authorization: $ANON_KEY" +``` + +As demonstrated in the video above, running Ollama locally is typically slower than running it in on a server with dedicated GPUs. We are collaborating with the Ollama team to improve local performance. + +In the future, a hosted Ollama API, will be provided as part of the Supabase platform. Supabase will scale and manage the API and GPUs for you. To sign up for early access, fill up [this form](https://forms.supabase.com/supabase.ai-llm-early-access). From 05eac4af510553ba859162a112463cd3b420c220 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 16 Apr 2024 21:27:11 +0700 Subject: [PATCH 03/18] Chore/switch index advisor installation to db extension (#22773) * Switch installing index advisor to via db extension rather than direct sql * Add CTA if index advisor is not available * Update language * Update copy * Fix checking index advisor ext --------- Co-authored-by: Alaister Young --- .../IndexAdvisorDisabledState.tsx | 67 +++-- .../QueryPerformanceV2/QueryIndexes.tsx | 14 +- .../database/index-advisor-enable-mutation.ts | 250 ------------------ 3 files changed, 49 insertions(+), 282 deletions(-) delete mode 100644 apps/studio/data/database/index-advisor-enable-mutation.ts diff --git a/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx b/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx index 8f277142a18..f25c2cf93ce 100644 --- a/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx +++ b/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx @@ -3,23 +3,23 @@ import { ExternalLink } from 'lucide-react' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useDatabaseExtensionEnableMutation } from 'data/database-extensions/database-extension-enable-mutation' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' -import { useIndexAdvisorEnableMutation } from 'data/database/index-advisor-enable-mutation' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui' import { Markdown } from '../Markdown' +import Link from 'next/link' +import { useParams } from 'common' export const IndexAdvisorDisabledState = () => { + const { ref } = useParams() const { project } = useProjectContext() const { data: extensions } = useDatabaseExtensionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const hypopgExtension = (extensions ?? []).find((ext) => ext.name === 'hypopg') + const indexAdvisorExtension = (extensions ?? []).find((ext) => ext.name === 'index_advisor') const { mutateAsync: enableExtension, isLoading: isEnablingExtension } = useDatabaseExtensionEnableMutation() - const { mutateAsync: enableIndexAdvisor, isLoading: isEnablingIndexAdvisor } = - useIndexAdvisorEnableMutation() - const isEnabling = isEnablingExtension || isEnablingIndexAdvisor const onEnableIndexAdvisor = async () => { if (project === undefined) return console.error('Project is required') @@ -32,33 +32,56 @@ export const IndexAdvisorDisabledState = () => { version: hypopgExtension.default_version, }) } - - // [Joshen] Once index_advisor ext is ready, it'll just be enabling an extension - await enableIndexAdvisor({ - projectRef: project.ref, - connectionString: project.connectionString, - }) + if (indexAdvisorExtension?.installed_version === null) { + await enableExtension({ + projectRef: project?.ref, + connectionString: project?.connectionString, + name: indexAdvisorExtension.name, + schema: indexAdvisorExtension?.schema ?? 'extensions', + version: indexAdvisorExtension.default_version, + }) + } } return ( - + - Get index suggestions to improve your query performance + - + +
- - + ) : ( + + )} +