Bỏ qua để vào nội dung chính
Semantic Search With Vercel AI SDK and Vector Embeddings

Semantic Search With Vercel AI SDK and Vector Embeddings

Bởi Henry Morgan
27 thg 3, 20267 phút đọc

Build a semantic search feature that understands meaning, not just keywords. Learn to generate embeddings with the Vercel AI SDK and query them with pgvector for intelligent search results.

Traditional keyword search matches exact terms. Semantic search understands meaning — a search for "car" finds documents about "vehicle", "automobile", and "driving". This is powered by vector embeddings: numerical representations of text that encode semantic meaning.

The Vercel AI SDK makes it straightforward to generate embeddings and the pgvector Postgres extension stores and queries them efficiently.

Setting Up pgvector

-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Add embedding column to your content table
ALTER TABLE articles ADD COLUMN embedding vector(1536);

-- Create index for fast similarity search
CREATE INDEX ON articles USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);

Generating Embeddings With the AI SDK

import { embed, embedMany } from 'ai';
import { openai } from '@ai-sdk/openai';

// Embed a single text
async function embedText(text: string) {
  const { embedding } = await embed({
    model: openai.embedding('text-embedding-3-small'),
    value: text,
  });
  return embedding; // number[]
}

// Embed multiple texts in one API call (more efficient)
async function embedArticles(articles: { id: string; content: string }[]) {
  const { embeddings } = await embedMany({
    model: openai.embedding('text-embedding-3-small'),
    values: articles.map(a => a.content),
  });
  return articles.map((a, i) => ({ id: a.id, embedding: embeddings[i] }));
}

Storing Embeddings in Postgres

import { db } from './db';

async function indexArticle(articleId: string, content: string) {
  const embedding = await embedText(content);
  
  // Store as pgvector format
  await db.$executeRaw`
    UPDATE articles
    SET embedding = ${JSON.stringify(embedding)}::vector
    WHERE id = ${articleId}
  `;
}

Building the Search API Route

// app/api/search/route.ts
import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';
import { db } from '@/lib/db';

export async function POST(request: Request) {
  const { query } = await request.json();
  
  // Embed the search query
  const { embedding } = await embed({
    model: openai.embedding('text-embedding-3-small'),
    value: query,
  });
  
  // Find the 5 most similar articles using cosine similarity
  const results = await db.$queryRaw`
    SELECT
      id,
      title,
      slug,
      summary,
      1 - (embedding <=> ${JSON.stringify(embedding)}::vector) AS similarity
    FROM articles
    WHERE status = 'PUBLISHED'
    ORDER BY embedding <=> ${JSON.stringify(embedding)}::vector
    LIMIT 5
  `;
  
  return Response.json({ results });
}

Building the Search UI

'use client';
import { useState } from 'react';
import { useDebounce } from 'use-debounce';

export function SemanticSearch() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [debouncedQuery] = useDebounce(query, 400);

  useEffect(() => {
    if (!debouncedQuery.trim()) return;
    fetch('/api/search', {
      method: 'POST',
      body: JSON.stringify({ query: debouncedQuery }),
    })
      .then(r => r.json())
      .then(d => setResults(d.results));
  }, [debouncedQuery]);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search by meaning..."
        className="w-full p-3 border rounded-lg"
      />
      <ul className="mt-4 space-y-2">
        {results.map(r => (
          <li key={r.id}>
            <a href={`/posts/${r.slug}`} className="text-blue-600 hover:underline">
              {r.title}
            </a>
            <span className="text-sm text-gray-500 ml-2">
              {Math.round(r.similarity * 100)}% match
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
}

Cost Considerations

text-embedding-3-small costs $0.02 per million tokens. For a site with 10,000 articles averaging 500 tokens each, initial indexing costs about $0.10. Queries cost fractions of a cent. Semantic search is one of the most cost-effective AI features you can add.

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan