> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/supabase/supabase/llms.txt
> Use this file to discover all available pages before exploring further.

# AI & Vector Examples

> Code examples for AI and vector operations with Supabase

Explore AI and vector examples covering embeddings, similarity search, semantic search, and AI integrations.

## Vector Embeddings Setup

### Enable pgvector Extension

```sql theme={null}
-- Enable the pgvector extension
create extension if not exists vector;

-- Create a table with vector column
create table documents (
  id bigserial primary key,
  content text,
  embedding vector(1536)
);

-- Create an index for fast similarity search
create index on documents using ivfflat (embedding vector_cosine_ops)
  with (lists = 100);
```

### Alternative: HNSW Index (Better Performance)

```sql theme={null}
-- Create HNSW index for better performance
create index on documents using hnsw (embedding vector_cosine_ops);
```

## Generate Embeddings

### Using OpenAI

```typescript theme={null}
import { createClient } from '@supabase/supabase-js'
import OpenAI from 'openai'

const supabase = createClient(url, key)
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

async function generateEmbedding(text: string) {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: text,
  })

  return response.data[0].embedding
}

async function storeDocument(content: string) {
  // Generate embedding
  const embedding = await generateEmbedding(content)

  // Store in database
  const { data, error } = await supabase
    .from('documents')
    .insert({
      content,
      embedding,
    })
    .select()
    .single()

  if (error) throw error
  return data
}
```

### Using Hugging Face

```typescript theme={null}
import { HfInference } from '@huggingface/inference'

const hf = new HfInference(process.env.HUGGINGFACE_API_KEY)

async function generateEmbeddingHF(text: string) {
  const response = await hf.featureExtraction({
    model: 'sentence-transformers/all-MiniLM-L6-v2',
    inputs: text,
  })

  return response
}
```

## Similarity Search

### Cosine Similarity

```typescript theme={null}
async function searchSimilarDocuments(query: string, matchCount: number = 5) {
  // Generate embedding for query
  const queryEmbedding = await generateEmbedding(query)

  // Search for similar documents
  const { data, error } = await supabase.rpc('match_documents', {
    query_embedding: queryEmbedding,
    match_count: matchCount,
  })

  if (error) throw error
  return data
}

// SQL function for similarity search
/*
create or replace function match_documents(
  query_embedding vector(1536),
  match_count int default 5
)
returns table (
  id bigint,
  content text,
  similarity float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    1 - (documents.embedding <=> query_embedding) as similarity
  from documents
  order by documents.embedding <=> query_embedding
  limit match_count;
end;
$$;
*/
```

### L2 Distance (Euclidean)

```sql theme={null}
create or replace function match_documents_l2(
  query_embedding vector(1536),
  match_count int default 5
)
returns table (
  id bigint,
  content text,
  distance float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    documents.embedding <-> query_embedding as distance
  from documents
  order by documents.embedding <-> query_embedding
  limit match_count;
end;
$$;
```

### Inner Product

```sql theme={null}
create or replace function match_documents_inner(
  query_embedding vector(1536),
  match_count int default 5
)
returns table (
  id bigint,
  content text,
  score float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    (documents.embedding <#> query_embedding) * -1 as score
  from documents
  order by documents.embedding <#> query_embedding
  limit match_count;
end;
$$;
```

## Semantic Search with Metadata

```typescript theme={null}
interface Document {
  id: number
  content: string
  metadata: {
    title: string
    author: string
    category: string
    created_at: string
  }
  embedding: number[]
}

async function semanticSearch(
  query: string,
  filters?: {
    category?: string
    author?: string
  },
  limit: number = 10
) {
  const queryEmbedding = await generateEmbedding(query)

  const { data, error } = await supabase.rpc('semantic_search', {
    query_embedding: queryEmbedding,
    match_count: limit,
    filter_category: filters?.category,
    filter_author: filters?.author,
  })

  if (error) throw error
  return data
}

// SQL function with filters
/*
create or replace function semantic_search(
  query_embedding vector(1536),
  match_count int default 10,
  filter_category text default null,
  filter_author text default null
)
returns table (
  id bigint,
  content text,
  metadata jsonb,
  similarity float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    documents.metadata,
    1 - (documents.embedding <=> query_embedding) as similarity
  from documents
  where
    (filter_category is null or documents.metadata->>'category' = filter_category)
    and (filter_author is null or documents.metadata->>'author' = filter_author)
  order by documents.embedding <=> query_embedding
  limit match_count;
end;
$$;
*/
```

## Hybrid Search (Vector + Full-Text)

```sql theme={null}
create or replace function hybrid_search(
  query_text text,
  query_embedding vector(1536),
  match_count int default 10
)
returns table (
  id bigint,
  content text,
  similarity float,
  fts_rank float,
  combined_rank float
)
language plpgsql
as $$
begin
  return query
  select
    documents.id,
    documents.content,
    1 - (documents.embedding <=> query_embedding) as similarity,
    ts_rank(documents.fts, plainto_tsquery(query_text)) as fts_rank,
    (1 - (documents.embedding <=> query_embedding)) * 0.7 +
    ts_rank(documents.fts, plainto_tsquery(query_text)) * 0.3 as combined_rank
  from documents
  where documents.fts @@ plainto_tsquery(query_text)
  order by combined_rank desc
  limit match_count;
end;
$$;
```

## RAG (Retrieval Augmented Generation)

```typescript theme={null}
import OpenAI from 'openai'

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

async function ragQuery(question: string) {
  // 1. Generate embedding for the question
  const questionEmbedding = await generateEmbedding(question)

  // 2. Find relevant documents
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: questionEmbedding,
    match_count: 5,
  })

  // 3. Build context from documents
  const context = documents
    .map((doc: any) => doc.content)
    .join('\n\n')

  // 4. Generate answer using GPT
  const completion = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: 'You are a helpful assistant. Answer the question based on the provided context. If you cannot answer based on the context, say so.',
      },
      {
        role: 'user',
        content: `Context:\n${context}\n\nQuestion: ${question}`,
      },
    ],
  })

  return {
    answer: completion.choices[0].message.content,
    sources: documents,
  }
}
```

## Streaming RAG Responses

```typescript theme={null}
async function* streamRAG(question: string) {
  // Get relevant context
  const questionEmbedding = await generateEmbedding(question)
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: questionEmbedding,
    match_count: 5,
  })

  const context = documents.map((doc: any) => doc.content).join('\n\n')

  // Stream response from OpenAI
  const stream = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: 'Answer based on the provided context.',
      },
      {
        role: 'user',
        content: `Context:\n${context}\n\nQuestion: ${question}`,
      },
    ],
    stream: true,
  })

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || ''
    if (content) {
      yield content
    }
  }
}

// Usage
for await (const chunk of streamRAG('What is Supabase?')) {
  process.stdout.write(chunk)
}
```

## Image Embeddings

```typescript theme={null}
import OpenAI from 'openai'

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

async function generateImageEmbedding(imageUrl: string) {
  // Use CLIP or similar model for image embeddings
  // This is a simplified example
  const response = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'clip-vit-base-patch32',
      input: imageUrl,
    }),
  })

  const data = await response.json()
  return data.data[0].embedding
}

async function searchSimilarImages(imageUrl: string) {
  const embedding = await generateImageEmbedding(imageUrl)

  const { data, error } = await supabase.rpc('match_images', {
    query_embedding: embedding,
    match_count: 10,
  })

  if (error) throw error
  return data
}
```

## Batch Processing

```typescript theme={null}
async function batchGenerateEmbeddings(texts: string[]) {
  const batchSize = 100
  const results: Array<{ text: string; embedding: number[] }> = []

  for (let i = 0; i < texts.length; i += batchSize) {
    const batch = texts.slice(i, i + batchSize)
    
    const embeddings = await Promise.all(
      batch.map((text) => generateEmbedding(text))
    )

    results.push(
      ...batch.map((text, index) => ({
        text,
        embedding: embeddings[index],
      }))
    )
  }

  return results
}

async function batchInsertDocuments(
  documents: Array<{ content: string; embedding: number[] }>
) {
  const { data, error } = await supabase
    .from('documents')
    .insert(documents)
    .select()

  if (error) throw error
  return data
}
```

## Clustering

```sql theme={null}
-- K-means clustering function
create or replace function kmeans_cluster(
  k int,
  max_iterations int default 100
)
returns table (
  id bigint,
  cluster_id int
)
language plpgsql
as $$
declare
  iteration int := 0;
  changed boolean := true;
begin
  -- Initialize cluster assignments randomly
  drop table if exists temp_clusters;
  create temp table temp_clusters as
  select id, floor(random() * k)::int as cluster_id
  from documents;

  while changed and iteration < max_iterations loop
    -- Update centroids and reassign
    -- (Simplified - actual implementation would be more complex)
    iteration := iteration + 1;
  end loop;

  return query select * from temp_clusters;
end;
$$;
```

## Deduplication

```typescript theme={null}
async function findDuplicates(threshold: number = 0.95) {
  const { data: allDocs } = await supabase
    .from('documents')
    .select('id, content, embedding')

  const duplicates: Array<{ id1: number; id2: number; similarity: number }> = []

  for (let i = 0; i < allDocs.length; i++) {
    for (let j = i + 1; j < allDocs.length; j++) {
      const similarity = cosineSimilarity(
        allDocs[i].embedding,
        allDocs[j].embedding
      )

      if (similarity >= threshold) {
        duplicates.push({
          id1: allDocs[i].id,
          id2: allDocs[j].id,
          similarity,
        })
      }
    }
  }

  return duplicates
}

function cosineSimilarity(a: number[], b: number[]): number {
  const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0)
  const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0))
  const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0))
  return dotProduct / (magnitudeA * magnitudeB)
}
```

## Edge Function for AI

```typescript theme={null}
// supabase/functions/generate-answer/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const { query } = await req.json()

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? ''
  )

  // Generate embedding
  const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'text-embedding-3-small',
      input: query,
    }),
  })

  const embeddingData = await embeddingResponse.json()
  const embedding = embeddingData.data[0].embedding

  // Search documents
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: embedding,
    match_count: 5,
  })

  const context = documents.map((d: any) => d.content).join('\n\n')

  // Generate answer
  const completionResponse = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: 'Answer based on the context provided.',
        },
        {
          role: 'user',
          content: `Context:\n${context}\n\nQuestion: ${query}`,
        },
      ],
    }),
  })

  const completion = await completionResponse.json()

  return new Response(
    JSON.stringify({
      answer: completion.choices[0].message.content,
      sources: documents,
    }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})
```

## Example Apps

<CardGroup cols={2}>
  <Card title="Vector Hello World" icon="rocket" href="https://github.com/supabase/supabase/blob/master/examples/ai/vector_hello_world.ipynb">
    Get started with vectors (Jupyter)
  </Card>

  <Card title="Image Search" icon="image" href="https://github.com/supabase/supabase/tree/master/examples/ai/image_search">
    Semantic image search app
  </Card>

  <Card title="Semantic Search" icon="magnifying-glass" href="https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions">
    Full-text + vector search
  </Card>

  <Card title="Chatbot with RAG" icon="comments" href="https://github.com/supabase/supabase/tree/master/examples/ai">
    AI chatbot with context
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="AI Documentation" icon="book" href="/ai/overview">
    Learn about AI features
  </Card>

  <Card title="pgvector Guide" icon="database" href="/ai/pgvector">
    Deep dive into pgvector
  </Card>

  <Card title="Vector Embeddings" icon="brain" href="/ai/vector-embeddings">
    Understanding embeddings
  </Card>

  <Card title="Similarity Search" icon="magnifying-glass" href="/ai/similarity-search">
    Advanced search techniques
  </Card>
</CardGroup>
