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>
53 lines
1.6 KiB
Python
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)
|
|
)
|
|
""")
|