Skip to content

systems embeddings and search

Claude edited this page Sep 23, 2026 · 1 revision

Embeddings and search

Active contributors: ferdiiskandar

Purpose

This system turns prompt text into a vector so the app can find similar prompts. It generates 1536-dimensional embeddings through OpenAI and runs cosine-similarity search inside PostgreSQL with pgvector. Similarity search is the only consumer of embeddings in the capsule.

Directory layout

lib/embeddings/
  generator.ts    OpenAI embedding calls
  similarity.ts   cosine similarity and pgvector search

Key abstractions

Symbol Kind Path
generateEmbedding single-text embedding lib/embeddings/generator.ts
generateEmbeddings batch embedding lib/embeddings/generator.ts
cosineSimilarity JavaScript similarity helper lib/embeddings/similarity.ts
findSimilarPrompts pgvector search function lib/embeddings/similarity.ts
SimilarPrompt result type lib/embeddings/similarity.ts
EMBEDDING_MODEL, EMBEDDING_DIMENSIONS model constants lib/constants.ts

How it works

generateEmbedding lazily creates one OpenAI client from OPENAI_API_KEY, then calls embeddings.create with EMBEDDING_MODEL (text-embedding-3-small) and EMBEDDING_DIMENSIONS (1536). Input is truncated to the first 8000 characters before the call. generateEmbeddings batches an array of texts with the same truncation and returns one vector per input in order.

findSimilarPrompts(text, userId, limit = 5, includePublic = true) embeds the query text, formats the vector as a bracket string, and runs a raw prisma.$queryRaw query against the prompts table. The query orders by the pgvector cosine distance operator embedding <=> vector and returns 1 - (embedding <=> vector) as similarity, limited to limit rows. It skips soft-deleted rows (deleted_at IS NULL) and rows without an embedding, and applies a visibility clause: with includePublic true the clause is user_id = <userId> OR is_public = true, otherwise only user_id = <userId>. The caller passes the userId as a bound parameter, so the clause is built with Prisma.sql rather than string concatenation. Results are mapped from snake_case columns back into PromptRecord shape.

cosineSimilarity(a, b) is a plain JavaScript implementation over the shorter of the two arrays. It is used for in-memory comparisons; the database search above does not depend on it.

The Prisma model exposes the column as embedding Unsupported("vector(1536)")?, because Prisma has no native vector type. That means the field cannot be read or written through the normal Prisma Client API, and all reads and writes of the column go through raw SQL. The vector extension is created by the initial migration.

graph TD
    Query[Query text] --> Generate[generateEmbedding]
    Generate --> OpenAI[OpenAI text-embedding-3-small]
    OpenAI --> Vector[1536-float vector]
    Vector --> Search[findSimilarPrompts raw query]
    Search --> DB[(prompts.embedding via pgvector)]
    DB --> Ranked[Ranked prompts with similarity]
Loading

Integration points

The Optimizer uses similarity search to suggest related or template prompts; see Optimizer. The Prompt.embedding column and its indexes are defined in Data and Prisma and Data models. This path requires OPENAI_API_KEY and does not go through the provider registry in LLM providers.

Entry points for modification

  • Change the embedding model or dimensions: lib/constants.ts (both constants must match the column type).
  • Change the input truncation or add retry logic: lib/embeddings/generator.ts.
  • Change search ranking, row limits, or the visibility clause: lib/embeddings/similarity.ts.
  • Change the column type: prisma/schema.prisma plus a new migration.

Key source files

File What it holds
lib/embeddings/generator.ts OpenAI embedding calls for one or many texts
lib/embeddings/similarity.ts Cosine helper and pgvector similarity search
lib/constants.ts Embedding model and dimension constants
prisma/schema.prisma Prompt.embedding unsupported vector column

Clone this wiki locally