Files
mule-image/backend/alembic/versions/0003_pgvector_embeddings.py
dtoro 649437dc85 feat: add embeddings pipeline and semantic search endpoint
Wire the full embedding flow:
- Rewrite Embedding model to use pgvector Vector(512) with HNSW index
- Add embed_photo, vision_fanout, backfill_vision Celery tasks on
  dedicated `vision` queue
- Hook vision_fanout into generate_thumbnails completion
- Add POST /api/v1/photos/search with hybrid RRF ranking (semantic-only
  for now; FTS leg added in PR5)
- Stub ocr_photo, detect_objects, extract_faces tasks for later PRs

Migration 0003 drops/recreates the embeddings table (was never populated).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:07:32 +02:00

53 lines
1.6 KiB
Python

"""pgvector embeddings
Revision ID: 0003_pgvector_embeddings
Revises: 0002_extend_tags
Create Date: 2026-04-10
Rewrite the embeddings table to use pgvector Vector(512) instead of
LargeBinary. Add composite PK (photo_id, model), created_at, and
HNSW index on vector column.
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0003_pgvector_embeddings"
down_revision: Union[str, None] = "0002_extend_tags"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Drop the old placeholder table and recreate with pgvector types.
# No data to preserve — it was never populated.
op.execute("DROP TABLE IF EXISTS embeddings")
op.execute("""
CREATE TABLE embeddings (
photo_id VARCHAR NOT NULL REFERENCES photos(id) ON DELETE CASCADE,
model VARCHAR(64) NOT NULL,
vector vector(512),
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (photo_id, model)
)
""")
# HNSW index for cosine similarity search.
# Defer creation on large backfills — drop and recreate afterward.
op.execute("""
CREATE INDEX IF NOT EXISTS ix_embeddings_vector_hnsw
ON embeddings USING hnsw (vector vector_cosine_ops)
""")
def downgrade() -> None:
op.execute("DROP TABLE IF EXISTS embeddings")
op.execute("""
CREATE TABLE embeddings (
photo_id VARCHAR NOT NULL REFERENCES photos(id) ON DELETE CASCADE,
model VARCHAR,
vector BYTEA,
PRIMARY KEY (photo_id)
)
""")