Supabase (BaaS)

Supabase Edge Functions & AI/Vector

Supabase Edge Functions (Deno, TypeScript, global) và AI/Vector (pgvector, RAG, semantic search, embeddings).

Edge Functions — Serverless tại Edge

Tổng quan

Supabase Edge Functions là các server-side TypeScript functions chạy trên Deno runtime, được phân phối toàn cầu tại edge -- gần với người dùng nhất để giảm latency. Mỗi function được deploy độc lập và có thể gọi từ client SDK hoặc HTTP request.

Deno, không phải Node.js: Edge Functions dùng Deno runtime, hỗ trợ TypeScript native và WASM. Import từ jsr: hoặc npm: specifiers. Cold starts có thể xảy ra -- thiết kế function ngắn, idempotent. Service role key thường được dùng trong Edge Function để bypass RLS khi cần truy cập toàn bộ dữ liệu.

Đặc điểm chính

Đặc điểmMô tả
RuntimeSupabase Edge Runtime (Deno-compatible, TypeScript first)
Global distributionTriển khai trên mạng lưới edge toàn cầu, gần user nhất
WASM supportHỗ trợ WebAssembly modules
Local dev paritySupabase CLI cung cấp runtime giống production
Secrets managementLưu credentials trong project secrets, truy cập qua Deno.env.get()
Time limitHàm được thiết kế cho tác vụ ngắn (thường dưới vài phút)

Quickstart

Tạo, deploy và gọi Edge Function đơn giản:

# Tạo function mới
supabase functions new hello-world

# Phát triển và test local
supabase functions serve

# Deploy lên production
supabase functions deploy hello-world

# Xóa function
supabase functions delete hello-world

# Danh sách functions
supabase functions list

Cấu trúc function cơ bản:

// supabase/functions/hello-world/index.ts

interface RequestBody {
  name: string
}

Deno.serve(async (req: Request) => {
  // Đọc body
  const { name } = await req.json() as RequestBody

  // Xử lý logic
  const data = {
    message: `Xin chào ${name}!`,
    timestamp: new Date().toISOString()
  }

  // Trả về response
  return new Response(JSON.stringify(data), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
})

Gọi function từ client:

// Gọi từ supabase-js
const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'Vinh' }
})

// Gọi trực tiếp qua HTTP
const response = await fetch(
  'https://[PROJECT_REF].supabase.co/functions/v1/hello-world',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ANON_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: 'Vinh' })
  }
)

CORS Handling

Khi Edge Function được gọi từ browser (frontend), cần xử lý CORS:

Deno.serve(async (req: Request) => {
  // Handle preflight OPTIONS request
  if (req.method === 'OPTIONS') {
    return new Response('ok', {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Authorization, Content-Type, apikey'
      }
    })
  }

  // Handle actual request
  const data = await req.json()

  return new Response(JSON.stringify({ success: true, data }), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
})

Luôn handle OPTIONS method cùng với các CORS headers trong mọi function được gọi từ browser.

Environment Variables & Secrets

Tuyệt đối không hardcode API keys trong code. Dùng Supabase Secrets:

# Set secrets qua CLI
supabase secrets set OPENAI_API_KEY=sk-xxx
supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx
supabase secrets set RESEND_API_KEY=re_xxx

# Set từ file .env
supabase secrets set --env-file .env.production

# Liệt kê secrets (chỉ hiển thị tên, không hiển thị giá trị)
supabase secrets list

# Xóa secret
supabase secrets unset MY_SECRET
// Truy cập secrets trong Edge Function
const OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!
const STRIPE_SECRET = Deno.env.get('STRIPE_SECRET_KEY')!
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')!
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!

Tích hợp với Supabase Services

Edge Function có thể tương tác với tất cả dịch vụ Supabase thông qua service role key:

import { createClient } from 'jsr:@supabase/supabase-js@2'

Deno.serve(async (req: Request) => {
  // Tạo Supabase client với service role để bypass RLS
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // Query database
  const { data: users } = await supabase
    .from('profiles')
    .select('id, email, full_name')
    .order('created_at', { ascending: false })

  // Upload file lên Storage
  const { data: upload } = await supabase.storage
    .from('exports')
    .upload('report.csv', csvContent)

  // Gọi Auth admin API (tạo user, xóa user...)
  const { data: newUser } = await supabase.auth.admin.createUser({
    email: 'new@example.com',
    password: 'secure-password',
    email_confirm: true
  })

  return new Response(JSON.stringify({ users, upload }), {
    headers: { 'Content-Type': 'application/json' }
  })
})

Khi nào dùng Edge Functions?

Edge Functions phù hợp cho nhiều use case:

Use caseVí dụ cụ thể
Webhook receiversStripe payment webhook, GitHub push events, Slack slash commands
API proxiesGọi OpenAI API (giấu API key), Resend transactional email
Custom business logicLogic validation phức tạp, tính toán không nên chạy trên client
Image/File processingResize ảnh server-side, generate PDF, extract metadata
Sending emailsGửi email xác nhận đơn hàng, welcome email, password reset
AI inferenceGọi OpenAI, HuggingFace, Claude từ edge
BotsDiscord Bot, Telegram Bot, Slack Bot
Scheduled tasksCron jobs gọi Edge Function định kỳ

Use Case Chi Tiết

1. Stripe Webhook Handler

// supabase/functions/stripe-webhook/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'
import Stripe from 'npm:stripe'

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

Deno.serve(async (req: Request) => {
  const signature = req.headers.get('stripe-signature')!
  const body = await req.text()

  // Verify webhook signature
  const event = stripe.webhooks.constructEvent(
    body,
    signature,
    Deno.env.get('STRIPE_WEBHOOK_SECRET')!
  )

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object
      const userId = session.client_reference_id

      // Cập nhật subscription trong database
      await supabase.from('subscriptions').upsert({
        user_id: userId,
        stripe_customer_id: session.customer,
        status: 'active',
        plan: session.metadata.plan,
        current_period_end: new Date(session.expires_at! * 1000).toISOString()
      })

      break
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object
      await supabase.from('subscriptions')
        .update({ status: 'canceled' })
        .eq('stripe_customer_id', subscription.customer)
      break
    }
  }

  return new Response(JSON.stringify({ received: true }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  })
})

2. OpenAI API Proxy

// supabase/functions/openai-proxy/index.ts

Deno.serve(async (req: Request) => {
  const { prompt, max_tokens } = await req.json()

  const response = 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-4o',
      messages: [{ role: 'user', content: prompt }],
      max_tokens: max_tokens || 500
    })
  })

  const data = await response.json()
  return new Response(JSON.stringify(data), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
})

3. Transactional Email với Resend

// supabase/functions/send-email/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'

const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')!

Deno.serve(async (req: Request) => {
  const { to, subject, html } = await req.json()

  const response = await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${RESEND_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      from: 'My App <noreply@myapp.com>',
      to: [to],
      subject: subject,
      html: html
    })
  })

  const data = await response.json()
  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' }
  })
})

4. Image Processing Pipeline

// supabase/functions/process-image/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'

Deno.serve(async (req: Request) => {
  const { imagePath, operations } = await req.json()

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // Download ảnh gốc
  const { data: imageBlob } = await supabase.storage
    .from('uploads')
    .download(imagePath)

  // Xử lý ảnh (ví dụ: thêm watermark, resize, compress)
  // ... processing logic ...

  // Upload ảnh đã xử lý
  await supabase.storage
    .from('processed')
    .upload(imagePath, processedBlob, { upsert: true })

  return new Response(JSON.stringify({ success: true }), {
    headers: { 'Content-Type': 'application/json' }
  })
})

Background Tasks & Cron

Edge Functions phù hợp cho tác vụ ngắn (vài giây đến vài phút). Với tác vụ dài hạn, kết hợp với Cron để chạy định kỳ:

-- Gọi Edge Function mỗi giờ để đồng bộ dữ liệu
SELECT cron.schedule(
  'hourly-sync',
  '0 * * * *',
  $$
  SELECT net.http_post(
    url := 'https://[PROJECT_REF].supabase.co/functions/v1/sync-data',
    headers := '{"Authorization": "Bearer [SERVICE_ROLE_KEY]"}'::jsonb,
    body := '{"source": "external-api"}'::jsonb
  )
  $$
);

-- Dọn dẹp dữ liệu cũ mỗi ngày lúc 3:00 AM
SELECT cron.schedule(
  'daily-cleanup',
  '0 3 * * *',
  $$
  SELECT net.http_post(
    url := 'https://[PROJECT_REF].supabase.co/functions/v1/cleanup',
    headers := '{"Authorization": "Bearer [SERVICE_ROLE_KEY]"}'::jsonb
  )
  $$
);

-- Xem danh sách cron jobs
SELECT * FROM cron.job;

-- Hủy cron job
SELECT cron.unschedule('hourly-sync');

Cron & Queues — Lập lịch và Hàng đợi

pg_cron — Scheduled Jobs trong Postgres

pg_cron cho phép lập lịch thực thi SQL hoặc gọi HTTP request định kỳ ngay trong database:

-- Enable extension
CREATE EXTENSION IF NOT EXISTS pg_cron;

-- Tạo job chạy SQL mỗi phút
SELECT cron.schedule(
  'cleanup-temp-tables',
  '* * * * *',
  'DELETE FROM temp_data WHERE created_at < now() - interval ''1 hour'''
);

-- Tạo job chạy vào 2:00 AM mỗi Chủ Nhật
SELECT cron.schedule(
  'weekly-report',
  '0 2 * * 0',
  $$
  SELECT net.http_post(
    url := 'https://[REF].supabase.co/functions/v1/generate-report',
    headers := '{"Authorization": "Bearer [KEY]"}'::jsonb
  )
  $$
);

Giới hạn: Khuyến nghị không quá 8 jobs chạy đồng thời, mỗi job không quá 10 phút.

pgmq — Message Queue trong Postgres

pgmq cung cấp message queue bền vững (persistent) ngay trong Postgres, không cần Redis hay RabbitMQ:

-- Enable extension và tạo queue
CREATE EXTENSION IF NOT EXISTS pgmq;
SELECT pgmq.create('email_queue');

-- Gửi message vào queue
SELECT pgmq.send('email_queue', '{
  "to": "user@example.com",
  "subject": "Welcome!",
  "template": "welcome_email"
}');

-- Consumer: đọc messages (visibility timeout 60 giây)
SELECT msg_id, read_ct, enqueued_at, vt, message
FROM pgmq.read('email_queue', 10, 60);

-- Xóa message sau khi xử lý thành công
SELECT pgmq.delete('email_queue', 42); -- msg_id = 42

-- Archive message (lưu lại để debug)
SELECT pgmq.archive('email_queue', 42);

-- Xóa queue
SELECT pgmq.drop_queue('email_queue');

Kiến trúc bất đồng bộ với Edge Functions + Queue:

Client gửi request -> Edge Function nhận và gửi message vào queue
                              |
                     Cron job đọc queue mỗi phút -> xử lý từng message
                              |
                     Kết quả lưu vào database -> Client poll hoặc subscribe

AI & Vectors — Ứng dụng Trí tuệ Nhân tạo

Tổng quan

Supabase cung cấp open source toolkit để phát triển ứng dụng AI trên Postgres và pgvector. Triết lý: "Vector database tốt nhất là database bạn đã có sẵn."

pgvector là lý do chính nhiều team chọn Supabase cho AI apps. Bạn không cần tách biệt vector DB -- embeddings được lưu trong cùng PostgreSQL với data gốc. RLS áp dụng cho cả vector search, đảm bảo bảo mật. Hybrid search (semantic + keyword) thường cho kết quả tốt hơn chỉ dùng một loại.

pgvector — Thiết lập và Index

-- Enable extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Tạo bảng lưu documents với embedding
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  embedding VECTOR(1536), -- 1536 dimensions (OpenAI text-embedding-3-small)
  metadata JSONB DEFAULT '{}',
  owner_id UUID REFERENCES auth.users(id),
  created_at TIMESTAMPTZ DEFAULT now()
);

-- HNSW index — nhanh nhưng tốn RAM (khuyên dùng cho production)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

-- IVFFlat index — ít tốn RAM hơn, chậm hơn HNSW
-- CREATE INDEX ON documents
-- USING ivfflat (embedding vector_cosine_ops)
-- WITH (lists = 100);

So sánh index types:

IndexTốc độRAMPhù hợp
HNSWRất nhanhCaoProduction, dataset lớn
IVFFlatKhá nhanhThấpDataset vừa, tiết kiệm RAM

3 Loại Tìm kiếm

Supabase hỗ trợ 3 loại tìm kiếm, có thể dùng riêng hoặc kết hợp:

Loại SearchCơ chếDùng khi
Semantic SearchTìm theo ý nghĩa (vector similarity)FAQ, documentation, chatbot knowledge base
Keyword SearchTìm theo từ khóa chính xác (Full Text Search)Product catalog, exact matching
Hybrid SearchKết hợp semantic + keywordCho kết quả toàn diện và chính xác nhất
-- Semantic Search: tìm documents tương tự với query embedding
CREATE OR REPLACE FUNCTION match_documents(
  query_embedding VECTOR(1536),
  match_threshold FLOAT DEFAULT 0.78,
  match_count INT DEFAULT 10
)
RETURNS TABLE (
  id BIGINT,
  title TEXT,
  content TEXT,
  similarity FLOAT
)
LANGUAGE SQL STABLE
AS $$
  SELECT
    id,
    title,
    content,
    1 - (embedding <=> query_embedding) AS similarity
  FROM documents
  WHERE 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
$$;

-- Keyword Search (Full Text Search)
SELECT id, title, ts_rank(to_tsvector('english', content), query) AS rank
FROM documents,
     websearch_to_tsquery('english', 'supabase authentication') AS query
WHERE to_tsvector('english', content) @@ query
ORDER BY rank DESC
LIMIT 10;
// Semantic search từ JavaScript client
const { data, error } = await supabase.rpc('match_documents', {
  query_embedding: embedding,
  match_threshold: 0.78,
  match_count: 10
})

// Keyword search từ JavaScript client
const { data, error } = await supabase
  .from('documents')
  .select('id, title, content')
  .textSearch('content', 'supabase authentication', {
    type: 'websearch',
    config: 'english'
  })

Generate Embeddings với Edge Functions

Sử dụng Edge Function để tạo embeddings an toàn (không expose API key):

// supabase/functions/generate-embedding/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'

const OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!

Deno.serve(async (req: Request) => {
  const { text } = await req.json()

  // Gọi OpenAI Embeddings API
  const response = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${OPENAI_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'text-embedding-3-small',
      input: text
    })
  })

  const { data } = await response.json()
  const embedding = data[0].embedding // [1536 số float]

  return new Response(JSON.stringify({ embedding }), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
})

RAG (Retrieval Augmented Generation)

Kiến trúc RAG cho phép LLM trả lời dựa trên dữ liệu của bạn:

1. EMBED    2. RETRIEVE          3. AUGMENT              4. GENERATE
Document  ─> Query embedding ─> Tìm top-k docs ─> Context + Query ─> LLM ─> Answer
  │              │                  │                    │
  │         user query          pgvector             prompt:
  │         -> embedding        similarity        "Answer based
  │         (OpenAI)            search               on context:"
  └─> Vector DB
      (pgvector)
// supabase/functions/rag-query/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'

const OPENAI_KEY = Deno.env.get('OPENAI_API_KEY')!
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

Deno.serve(async (req: Request) => {
  const { query, userId } = await req.json()

  // Step 1: Tạo embedding cho câu hỏi
  const embedRes = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${OPENAI_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'text-embedding-3-small',
      input: query
    })
  })
  const { data: [{ embedding }] } = await embedRes.json()

  // Step 2: Tìm documents liên quan (RLS-aware)
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: embedding,
    match_count: 5
  })

  // Step 3 & 4: Gửi context + query cho LLM
  const context = documents
    .map(d => `[${d.title}]\n${d.content}`)
    .join('\n\n---\n\n')

  const chatRes = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${OPENAI_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o',
      messages: [
        {
          role: 'system',
          content: `Bạn là trợ lý AI. Chỉ trả lời dựa trên context được cung cấp.
          Nếu context không đủ thông tin, hãy nói "Tôi không có đủ thông tin để trả lời."
          Luôn trích dẫn nguồn từ title của document.`
        },
        {
          role: 'user',
          content: `Context:\n${context}\n\nCâu hỏi: ${query}`
        }
      ],
      temperature: 0.3
    })
  })

  const answer = await chatRes.json()

  return new Response(JSON.stringify({
    answer: answer.choices[0].message.content,
    sources: documents.map(d => ({ title: d.title, similarity: d.similarity }))
  }), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
})

RAG với Row Level Security

Supabase hỗ trợ RAG with RLS -- đảm bảo mỗi user chỉ search được documents họ có quyền truy cập:

-- Policy: user chỉ search được documents của chính họ hoặc public
CREATE POLICY "Users can only search own docs"
ON documents FOR SELECT
USING (
  auth.uid() = owner_id
  OR is_public = true
);

-- Policy cho tổ chức: team members có thể search documents của team
CREATE POLICY "Team members can search team docs"
ON documents FOR SELECT
USING (
  team_id IN (
    SELECT team_id FROM team_members WHERE user_id = auth.uid()
  )
);

Khi gọi match_documents() từ Edge Function với service role key, RLS bị bypass. Để RLS hoạt động, gọi function với user JWT:

// Dùng user's JWT thay vì service role key
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_ANON_KEY')!,
  {
    global: {
      headers: {
        Authorization: `Bearer ${userJWT}`
      }
    }
  }
)

Integrations

Supabase tích hợp với nhiều nền tảng AI/ML:

IntegrationCông dụng
OpenAIEmbeddings (text-embedding-3-small/large), ChatGPT/GPT-4o completions
HuggingFace100,000+ ML models, Inference API, open-source embeddings
LangChainFramework phát triển LLM apps với vector stores, chains, agents
LlamaIndexData framework cho LLM, indexing, querying
Amazon BedrockFoundation models từ AWS (Claude, Llama, Titan)
Google ColabNotebooks cho AI development, prototyping
RoboflowComputer vision, object detection integration

Vector Buckets

Loại bucket mới trong Supabase Storage dành riêng cho vector operations:

  • HNSW indexing: Fast approximate nearest neighbor search
  • Distance metrics: Cosine, Euclidean, Inner Product
  • Metadata filtering: Lọc kết quả theo metadata kèm vector
  • Local development: Hỗ trợ dev local với Supabase CLI

Compute cho AI Workloads

Vector search production cần compute cao:

  • Large hoặc XL compute cho vector search production
  • Cần RAM cao để HNSW index nạp vào memory
  • IOPS cao cho parallel similarity queries
  • Nếu dataset > 1M vectors, cân nhắc 2XL hoặc cao hơn

Bài tập

Bài tập 5: Stripe Checkout Webhook Handler

Yêu cầu:

  • Tạo Edge Function nhận webhook từ Stripe khi thanh toán hoàn tất
  • Verify webhook signature để đảm bảo request từ Stripe
  • Khi checkout.session.completed: cập nhật subscription status trong database
  • Khi customer.subscription.deleted: đánh dấu subscription canceled
  • Log tất cả events vào bảng webhook_logs để debug
  • Xử lý idempotency (tránh xử lý trùng lặp)

Gợi ý triển khai:

// supabase/functions/stripe-webhook/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2'
import Stripe from 'npm:stripe'

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

Deno.serve(async (req: Request) => {
  // 1. Verify signature
  // 2. Handle event types
  // 3. Log to webhook_logs table
  // 4. Return success
})

Bài tập 6: Contact Form API với Email Notification

Yêu cầu:

  • Tạo Edge Function xử lý form liên hệ từ website
  • Validate input (tên, email, nội dung không được trống)
  • Lưu submission vào database
  • Gửi email thông báo cho admin qua Resend
  • Gửi email xác nhận cho người gửi
  • Rate limiting: mỗi IP chỉ được gửi 3 form/giờ
  • Trả về CAPTCHA token verification (Turnstile)

Bài tập 7: Semantic Search cho Knowledge Base

Yêu cầu:

  • Tạo bảng articles với title, content, embedding (VECTOR)
  • Viết Edge Function để tạo và lưu embedding khi thêm/sửa article
  • Viết hàm match_articles trong Postgres để semantic search
  • Hiển thị kết quả với similarity score, highlight đoạn text liên quan
  • Hỗ trợ kết hợp keyword search + semantic search (hybrid)

Gợi ý triển khai:

// supabase/functions/index-article/index.ts
// Tự động tạo embedding khi article được tạo/cập nhật

Deno.serve(async (req: Request) => {
  const { article_id, title, content } = await req.json()

  // Tạo embedding từ title + content
  const text = `${title}\n\n${content}`
  const embedding = await generateEmbedding(text)

  // Lưu embedding vào article
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  await supabase.from('articles')
    .update({ embedding })
    .eq('id', article_id)

  return new Response(JSON.stringify({ success: true }))
})

Bài tập 8: RAG Chatbot với Document Context

Yêu cầu:

  • Upload PDF/text documents, chunk thành các đoạn nhỏ (500-1000 tokens)
  • Tạo embedding cho từng chunk, lưu vào pgvector
  • Xây dựng chat UI: user gửi câu hỏi, system tìm context + gọi LLM
  • Hiển thị sources (trích dẫn document + chunk) kèm câu trả lời
  • Xử lý streaming response từ LLM (hiển thị từng chữ)
  • Hỗ trợ follow-up questions (giữ conversation history)

Kiến trúc gợi ý:

Upload Doc -> Chunking -> Embedding -> pgvector
                                         |
User Query -> Embedding -> Vector Search -> Top-K Chunks
                                               |
                                    Context + Query -> OpenAI -> Answer + Sources

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.