pgvector & Vector Search
How HyperSaaS uses pgvector for document embeddings and similarity search.
Why pgvector?
Instead of running a separate vector database (Pinecone, Weaviate, Qdrant), HyperSaaS keeps embeddings inside PostgreSQL using pgvector. This means:
- No extra infrastructure to manage
- Transactional consistency with your relational data
- Standard SQL queries with vector operations
- Production-ready up to ~10M vectors with HNSW indexing
Setup
pgvector must be installed as a PostgreSQL extension. The Docker image pgvector/pgvector:pg17 includes it pre-installed. For manual setups:
CREATE EXTENSION IF NOT EXISTS vector;The Django migration handles this automatically via VectorExtension().
DocumentChunk Model
Each document chunk stores its text content alongside a 1536-dimensional embedding vector:
class DocumentChunk(BaseModel):
document = models.ForeignKey(Document, on_delete=models.CASCADE)
chunk_index = models.PositiveIntegerField()
content = models.TextField() # Raw text
embedding = VectorField(dimensions=1536) # pgvector
embedding_model = models.CharField(max_length=100)
page_number = models.PositiveIntegerField(null=True)
section_heading = models.CharField(max_length=500, blank=True)
token_count = models.PositiveIntegerField(default=0)
chunk_metadata = models.JSONField(default=dict) # Docling metadataHNSW Index
An HNSW (Hierarchical Navigable Small World) index enables fast approximate nearest-neighbor search:
HnswIndex(
name="doc_chunk_embedding_hnsw_idx",
fields=["embedding"],
m=16, # Connections per node
ef_construction=64, # Build-time search width
opclasses=["vector_cosine_ops"], # Cosine distance
)Index parameters
| Parameter | Value | Effect |
|---|---|---|
m | 16 | Higher = better recall, more memory |
ef_construction | 64 | Higher = better index quality, slower build |
opclasses | vector_cosine_ops | Cosine similarity distance metric |
Querying
Semantic search uses pgvector's CosineDistance annotation:
from pgvector.django import CosineDistance
DocumentChunk.objects.filter(
document_id__in=document_ids
).annotate(
distance=CosineDistance("embedding", query_embedding)
).order_by("distance")[:top_k]The score is converted to similarity: score = 1.0 - distance.
Embedding Model
HyperSaaS uses OpenAI's text-embedding-3-small (1536 dimensions) by default. This is configurable:
| Setting | Default | Description |
|---|---|---|
DOCUMENT_EMBEDDING_MODEL | text-embedding-3-small | OpenAI model name |
DOCUMENT_EMBEDDING_DIMENSIONS | 1536 | Must match VectorField dimensions |
DOCUMENT_EMBEDDING_BATCH_SIZE | 512 | Texts per API call |