Spaces:
Sleeping
Sleeping
Commit Β·
99cc0f4
1
Parent(s): 70bc888
feat: sprint 17 - linguistic hegemony & vector synchronization
Browse files- Dockerfile +39 -8
- __pycache__/ai_brain.cpython-312.pyc +0 -0
- __pycache__/api.cpython-312.pyc +0 -0
- __pycache__/bhashini.cpython-312.pyc +0 -0
- __pycache__/config.cpython-312.pyc +0 -0
- __pycache__/scraper.cpython-312.pyc +0 -0
- api.py +10 -5
- bhashini.py +2 -2
- config.py +38 -2
- requirements.txt +31 -10
- scripts/backfill_768.py +162 -0
Dockerfile
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
# Layer 1:
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
| 7 |
build-essential \
|
| 8 |
git \
|
|
|
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
-
# Layer 2:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
COPY requirements.txt .
|
| 13 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
|
| 15 |
-
# Layer
|
| 16 |
-
# This
|
|
|
|
| 17 |
RUN python -c "\
|
| 18 |
from sentence_transformers import SentenceTransformer, CrossEncoder; \
|
| 19 |
SentenceTransformer('nomic-ai/nomic-embed-text-v1', cache_folder='/app/models', trust_remote_code=True); \
|
| 20 |
-
print('β Nomic Embedding model cached'); \
|
| 21 |
CrossEncoder('cross-encoder/ettin-reranker-68m-v1', max_length=512); \
|
| 22 |
print('β Ettin Reranker cached')"
|
| 23 |
|
| 24 |
-
# Layer
|
| 25 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
COPY . .
|
| 27 |
|
| 28 |
EXPOSE 7860
|
|
|
|
| 1 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
+
# GovBridge India β Production Dockerfile
|
| 3 |
+
# Target: Hugging Face Spaces (CPU Basic: 2 vCPU, 16GB RAM)
|
| 4 |
+
# Constraint: ZERO CUDA binaries. CPU-only PyTorch wheels.
|
| 5 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 6 |
+
|
| 7 |
FROM python:3.11-slim
|
| 8 |
|
| 9 |
WORKDIR /app
|
| 10 |
|
| 11 |
+
# ββ Layer 1: OS Dependencies (ROOT) ββββββββββββββββββββββββββ
|
| 12 |
+
# libgomp1: Required for OpenMP multi-threading (IndicTrans2 + PyTorch CPU)
|
| 13 |
+
# build-essential: Required for Cython compilation (IndicTransToolkit)
|
| 14 |
+
# git: Required for pip install from GitHub repos
|
| 15 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 16 |
build-essential \
|
| 17 |
git \
|
| 18 |
+
libgomp1 \
|
| 19 |
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
| 21 |
+
# ββ Layer 2: CPU-Only PyTorch Installation βββββββββββββββββββ
|
| 22 |
+
# CRITICAL: This MUST run BEFORE requirements.txt to prevent
|
| 23 |
+
# pip from resolving torch from the default PyPI index (which
|
| 24 |
+
# downloads 1.2GB+ of CUDA/NVIDIA binaries and exhausts disk).
|
| 25 |
+
RUN pip install --no-cache-dir \
|
| 26 |
+
torch>=2.2.0 \
|
| 27 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 28 |
+
|
| 29 |
+
# ββ Layer 3: Python Dependencies βββββββββββββββββββββββββββββ
|
| 30 |
COPY requirements.txt .
|
| 31 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 32 |
|
| 33 |
+
# ββ Layer 4: Pre-cache ALL ML models at build time βββββββββββ
|
| 34 |
+
# This eliminates cold-start latency on first request.
|
| 35 |
+
# Models are cached to /app/models (persisted across container restarts).
|
| 36 |
RUN python -c "\
|
| 37 |
from sentence_transformers import SentenceTransformer, CrossEncoder; \
|
| 38 |
SentenceTransformer('nomic-ai/nomic-embed-text-v1', cache_folder='/app/models', trust_remote_code=True); \
|
| 39 |
+
print('β Nomic Embedding model (768-dim) cached'); \
|
| 40 |
CrossEncoder('cross-encoder/ettin-reranker-68m-v1', max_length=512); \
|
| 41 |
print('β Ettin Reranker cached')"
|
| 42 |
|
| 43 |
+
# ββ Layer 5: Pre-cache IndicTrans2 translation model βββββββββ
|
| 44 |
+
# Cached separately because it requires trust_remote_code and
|
| 45 |
+
# downloads ~800MB of weights. Failure here must NOT block deployment.
|
| 46 |
+
RUN python -c "\
|
| 47 |
+
try: \
|
| 48 |
+
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM; \
|
| 49 |
+
AutoTokenizer.from_pretrained('ai4bharat/indictrans2-en-indic-dist-200M', trust_remote_code=True, cache_dir='/app/models/indictrans2'); \
|
| 50 |
+
AutoModelForSeq2SeqLM.from_pretrained('ai4bharat/indictrans2-en-indic-dist-200M', trust_remote_code=True, cache_dir='/app/models/indictrans2'); \
|
| 51 |
+
print('β IndicTrans2 translation model cached'); \
|
| 52 |
+
except Exception as e: \
|
| 53 |
+
print(f'β οΈ IndicTrans2 cache failed (non-blocking): {e}')"
|
| 54 |
+
|
| 55 |
+
# ββ Layer 6: Copy application code LAST ββββββββββββββββββββββ
|
| 56 |
+
# Code changes don't invalidate the expensive model cache layers.
|
| 57 |
COPY . .
|
| 58 |
|
| 59 |
EXPOSE 7860
|
__pycache__/ai_brain.cpython-312.pyc
DELETED
|
Binary file (1.63 kB)
|
|
|
__pycache__/api.cpython-312.pyc
DELETED
|
Binary file (19.4 kB)
|
|
|
__pycache__/bhashini.cpython-312.pyc
DELETED
|
Binary file (7.2 kB)
|
|
|
__pycache__/config.cpython-312.pyc
DELETED
|
Binary file (810 Bytes)
|
|
|
__pycache__/scraper.cpython-312.pyc
DELETED
|
Binary file (9.94 kB)
|
|
|
api.py
CHANGED
|
@@ -20,9 +20,9 @@ from config import settings
|
|
| 20 |
|
| 21 |
# --- SECURE KEYS ---
|
| 22 |
SUPABASE_URL = settings.SUPABASE_URL
|
| 23 |
-
SUPABASE_KEY = settings.SUPABASE_KEY
|
| 24 |
-
GROQ_API_KEY =
|
| 25 |
-
ADMIN_SECRET =
|
| 26 |
|
| 27 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 28 |
groq_client = Groq(api_key=GROQ_API_KEY)
|
|
@@ -197,8 +197,9 @@ async def ingest_document(request: IngestRequest, admin_key: str = ""):
|
|
| 197 |
prefixed_chunks = [f"search_document: {c}" for c in chunks]
|
| 198 |
embeddings = model.encode(prefixed_chunks, normalize_embeddings=True, batch_size=32, show_progress_bar=False).tolist()
|
| 199 |
rows = []
|
|
|
|
| 200 |
for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)):
|
| 201 |
-
|
| 202 |
"chunk_index": i,
|
| 203 |
"chunk_text": chunk,
|
| 204 |
"scheme_title": request.title,
|
|
@@ -208,7 +209,11 @@ async def ingest_document(request: IngestRequest, admin_key: str = ""):
|
|
| 208 |
"doc_type": request.doc_type,
|
| 209 |
"embedding": embedding,
|
| 210 |
"content_hash": doc_hash
|
| 211 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
supabase.table("document_chunks").upsert(rows, on_conflict="content_hash,chunk_index").execute()
|
| 213 |
return {"status": "success", "title": request.title, "chunks_created": len(chunks), "doc_hash": doc_hash}
|
| 214 |
|
|
|
|
| 20 |
|
| 21 |
# --- SECURE KEYS ---
|
| 22 |
SUPABASE_URL = settings.SUPABASE_URL
|
| 23 |
+
SUPABASE_KEY = settings.SUPABASE_KEY
|
| 24 |
+
GROQ_API_KEY = settings.GROQ_API_KEY
|
| 25 |
+
ADMIN_SECRET = settings.ADMIN_SECRET
|
| 26 |
|
| 27 |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 28 |
groq_client = Groq(api_key=GROQ_API_KEY)
|
|
|
|
| 197 |
prefixed_chunks = [f"search_document: {c}" for c in chunks]
|
| 198 |
embeddings = model.encode(prefixed_chunks, normalize_embeddings=True, batch_size=32, show_progress_bar=False).tolist()
|
| 199 |
rows = []
|
| 200 |
+
dual_write = settings.DUAL_WRITE_EMBEDDINGS.lower() == "true"
|
| 201 |
for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)):
|
| 202 |
+
row = {
|
| 203 |
"chunk_index": i,
|
| 204 |
"chunk_text": chunk,
|
| 205 |
"scheme_title": request.title,
|
|
|
|
| 209 |
"doc_type": request.doc_type,
|
| 210 |
"embedding": embedding,
|
| 211 |
"content_hash": doc_hash
|
| 212 |
+
}
|
| 213 |
+
# During expand-contract migration: write to BOTH columns
|
| 214 |
+
if dual_write:
|
| 215 |
+
row["embedding_v2"] = embedding
|
| 216 |
+
rows.append(row)
|
| 217 |
supabase.table("document_chunks").upsert(rows, on_conflict="content_hash,chunk_index").execute()
|
| 218 |
return {"status": "success", "title": request.title, "chunks_created": len(chunks), "doc_hash": doc_hash}
|
| 219 |
|
bhashini.py
CHANGED
|
@@ -88,7 +88,7 @@ def local_indictrans_translate(text: str, src: str, tgt: str) -> str:
|
|
| 88 |
return_attention_mask=True
|
| 89 |
)
|
| 90 |
|
| 91 |
-
with torch.
|
| 92 |
generated_tokens = _indictrans_model.generate(
|
| 93 |
**inputs,
|
| 94 |
num_beams=4,
|
|
@@ -120,7 +120,7 @@ def local_indictrans_translate(text: str, src: str, tgt: str) -> str:
|
|
| 120 |
truncation=True,
|
| 121 |
max_length=512
|
| 122 |
)
|
| 123 |
-
with torch.
|
| 124 |
outputs = _indictrans_model.generate(
|
| 125 |
**inputs,
|
| 126 |
tgt_lang=tgt_code,
|
|
|
|
| 88 |
return_attention_mask=True
|
| 89 |
)
|
| 90 |
|
| 91 |
+
with torch.inference_mode():
|
| 92 |
generated_tokens = _indictrans_model.generate(
|
| 93 |
**inputs,
|
| 94 |
num_beams=4,
|
|
|
|
| 120 |
truncation=True,
|
| 121 |
max_length=512
|
| 122 |
)
|
| 123 |
+
with torch.inference_mode():
|
| 124 |
outputs = _indictrans_model.generate(
|
| 125 |
**inputs,
|
| 126 |
tgt_lang=tgt_code,
|
config.py
CHANGED
|
@@ -1,12 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 2 |
from pydantic import Field
|
| 3 |
from typing import Optional
|
| 4 |
-
|
| 5 |
|
| 6 |
class Settings(BaseSettings):
|
|
|
|
| 7 |
SUPABASE_URL: str = Field(default="")
|
| 8 |
SUPABASE_KEY: str = Field(default="")
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
settings = Settings()
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GovBridge India β Centralized Configuration
|
| 3 |
+
All environment variables are declared here with Pydantic validation.
|
| 4 |
+
Usage: from config import settings
|
| 5 |
+
"""
|
| 6 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 7 |
from pydantic import Field
|
| 8 |
from typing import Optional
|
| 9 |
+
|
| 10 |
|
| 11 |
class Settings(BaseSettings):
|
| 12 |
+
# ββ Database βββββββββββββββββββββββββββββββββββββββββ
|
| 13 |
SUPABASE_URL: str = Field(default="")
|
| 14 |
SUPABASE_KEY: str = Field(default="")
|
| 15 |
|
| 16 |
+
# ββ LLM ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
GROQ_API_KEY: str = Field(default="")
|
| 18 |
+
|
| 19 |
+
# ββ Admin ββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
+
ADMIN_SECRET: str = Field(default="change-this-in-production")
|
| 21 |
+
|
| 22 |
+
# ββ Translation (Bhashini) βββββββββββββββββββββββββββ
|
| 23 |
+
BHASHINI_USER_ID: Optional[str] = Field(default=None)
|
| 24 |
+
BHASHINI_API_KEY: Optional[str] = Field(default=None)
|
| 25 |
+
|
| 26 |
+
# ββ WhatsApp βββββββββββββββββββββββββββββββββββββββββ
|
| 27 |
+
WHATSAPP_TOKEN: Optional[str] = Field(default=None)
|
| 28 |
+
WHATSAPP_PHONE_NUMBER_ID: Optional[str] = Field(default=None)
|
| 29 |
+
WHATSAPP_VERIFY_TOKEN: Optional[str] = Field(default=None)
|
| 30 |
+
WHATSAPP_APP_SECRET: Optional[str] = Field(default=None)
|
| 31 |
+
|
| 32 |
+
# ββ Feature Flags ββββββββββββββββββββββββββββββββββββ
|
| 33 |
+
ENVIRONMENT: str = Field(default="development")
|
| 34 |
+
|
| 35 |
+
# ββ Migration Flag βββββββββββββββββββββββββββββββββββ
|
| 36 |
+
# Set to "true" during the expand-contract migration window.
|
| 37 |
+
# When true, ingest writes to BOTH embedding AND embedding_v2.
|
| 38 |
+
# After migration 009 is complete, set back to "false".
|
| 39 |
+
DUAL_WRITE_EMBEDDINGS: str = Field(default="false")
|
| 40 |
+
|
| 41 |
+
model_config = SettingsConfigDict(
|
| 42 |
+
env_file=".env",
|
| 43 |
+
env_file_encoding="utf-8",
|
| 44 |
+
extra="ignore"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
|
| 48 |
settings = Settings()
|
requirements.txt
CHANGED
|
@@ -1,22 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
fastapi
|
| 2 |
uvicorn[standard]
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
| 4 |
sentence-transformers>=2.7.0
|
| 5 |
einops
|
| 6 |
huggingface-hub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
groq
|
|
|
|
|
|
|
| 8 |
supabase
|
|
|
|
|
|
|
| 9 |
slowapi
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
sentencepiece>=0.2.0
|
| 13 |
-
sacremoses>=0.1.1
|
| 14 |
httpx
|
|
|
|
|
|
|
| 15 |
feedparser
|
| 16 |
-
IndicTransToolkit
|
| 17 |
-
onnx
|
| 18 |
-
onnxruntime
|
| 19 |
-
optimum
|
| 20 |
-
pydantic-settings
|
| 21 |
tenacity
|
| 22 |
pydantic
|
|
|
|
|
|
| 1 |
+
# βββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
+
# GovBridge India β Python Dependencies
|
| 3 |
+
# Target: Hugging Face Spaces (CPU Basic: 2 vCPU, 16GB RAM)
|
| 4 |
+
# CRITICAL: All ML wheels MUST be CPU-only to prevent disk exhaustion
|
| 5 |
+
# βββββββββββββββββββββββββββββββββββββββββββ
|
| 6 |
+
|
| 7 |
+
# --- Core Web Framework ---
|
| 8 |
fastapi
|
| 9 |
uvicorn[standard]
|
| 10 |
+
|
| 11 |
+
# --- ML / AI (CPU-ONLY WHEELS) ---
|
| 12 |
+
# CRITICAL: --extra-index-url in Dockerfile forces CPU torch
|
| 13 |
+
# DO NOT add torch here β it is installed separately in Dockerfile Layer 2
|
| 14 |
sentence-transformers>=2.7.0
|
| 15 |
einops
|
| 16 |
huggingface-hub
|
| 17 |
+
|
| 18 |
+
# --- Translation Pipeline ---
|
| 19 |
+
# CRITICAL PIN: transformers==4.40.2 is the exact temporal anchor
|
| 20 |
+
# that supports IndicTrans2 WITHOUT the ONNX removal crash.
|
| 21 |
+
# Versions >=4.41.0 removed transformers.onnx, causing ImportError.
|
| 22 |
+
transformers==4.40.2
|
| 23 |
+
sentencepiece>=0.2.0
|
| 24 |
+
sacremoses>=0.1.1
|
| 25 |
+
IndicTransToolkit
|
| 26 |
+
|
| 27 |
+
# --- LLM Inference ---
|
| 28 |
groq
|
| 29 |
+
|
| 30 |
+
# --- Database ---
|
| 31 |
supabase
|
| 32 |
+
|
| 33 |
+
# --- Rate Limiting (in-process, for local dev) ---
|
| 34 |
slowapi
|
| 35 |
+
|
| 36 |
+
# --- HTTP Client ---
|
|
|
|
|
|
|
| 37 |
httpx
|
| 38 |
+
|
| 39 |
+
# --- Data Processing ---
|
| 40 |
feedparser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
tenacity
|
| 42 |
pydantic
|
| 43 |
+
pydantic-settings
|
scripts/backfill_768.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GovBridge India β Phase 2: Backfill 768-dim Embeddings
|
| 3 |
+
Sprint 17: Zero-Downtime Expand-Contract Migration
|
| 4 |
+
|
| 5 |
+
PREREQUISITES:
|
| 6 |
+
1. Migration 007 has been executed (embedding_v2 column exists)
|
| 7 |
+
2. Nomic model is available (pip install sentence-transformers einops)
|
| 8 |
+
|
| 9 |
+
USAGE:
|
| 10 |
+
cd /workspaces/govbridge
|
| 11 |
+
python3 gov_backend/scripts/backfill_768.py
|
| 12 |
+
|
| 13 |
+
BEHAVIOR:
|
| 14 |
+
- Reads ALL rows from document_chunks where embedding_v2 IS NULL
|
| 15 |
+
- Generates 768-dim embeddings using nomic-embed-text-v1
|
| 16 |
+
- UPSERTs in batches of 50 rows using keyset pagination
|
| 17 |
+
- Also backfills query_cache.query_embedding_v2
|
| 18 |
+
- Safe to re-run (idempotent via IS NULL filter)
|
| 19 |
+
"""
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import time
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
# Ensure gov_backend is on the Python path
|
| 26 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 27 |
+
from config import settings
|
| 28 |
+
|
| 29 |
+
from supabase import create_client
|
| 30 |
+
from sentence_transformers import SentenceTransformer
|
| 31 |
+
|
| 32 |
+
# ββ Configuration ββββββββββββββββββββββββββββββββββββββββββββ
|
| 33 |
+
BATCH_SIZE = 50 # Rows per batch (free-tier Supabase safe)
|
| 34 |
+
SLEEP_BETWEEN = 0.5 # Seconds between batches (prevent rate limit)
|
| 35 |
+
MAX_ROWS = 5000 # Safety ceiling per run
|
| 36 |
+
|
| 37 |
+
def main():
|
| 38 |
+
supabase_url = settings.SUPABASE_URL
|
| 39 |
+
supabase_key = settings.SUPABASE_KEY
|
| 40 |
+
|
| 41 |
+
if not supabase_url or not supabase_key:
|
| 42 |
+
print("β FATAL: Missing SUPABASE_URL or SUPABASE_KEY in environment")
|
| 43 |
+
sys.exit(1)
|
| 44 |
+
|
| 45 |
+
print("β³ Loading Nomic Embedding Model (768-dim)...")
|
| 46 |
+
model = SentenceTransformer(
|
| 47 |
+
'nomic-ai/nomic-embed-text-v1',
|
| 48 |
+
trust_remote_code=True
|
| 49 |
+
)
|
| 50 |
+
print("β
Nomic model loaded")
|
| 51 |
+
|
| 52 |
+
supabase = create_client(supabase_url, supabase_key)
|
| 53 |
+
|
| 54 |
+
# ββ Phase 2A: Backfill document_chunks.embedding_v2 ββββββ
|
| 55 |
+
print("\nβββ PHASE 2A: Backfilling document_chunks.embedding_v2 βββ")
|
| 56 |
+
|
| 57 |
+
result = supabase.table('document_chunks') \
|
| 58 |
+
.select('id, chunk_text, scheme_title') \
|
| 59 |
+
.is_('embedding_v2', 'null') \
|
| 60 |
+
.limit(MAX_ROWS) \
|
| 61 |
+
.execute()
|
| 62 |
+
|
| 63 |
+
chunks = result.data or []
|
| 64 |
+
total = len(chunks)
|
| 65 |
+
|
| 66 |
+
if total == 0:
|
| 67 |
+
print("π document_chunks: All rows already have embedding_v2")
|
| 68 |
+
else:
|
| 69 |
+
print(f"Found {total} chunks to backfill")
|
| 70 |
+
|
| 71 |
+
for i in range(0, total, BATCH_SIZE):
|
| 72 |
+
batch = chunks[i:i + BATCH_SIZE]
|
| 73 |
+
batch_num = (i // BATCH_SIZE) + 1
|
| 74 |
+
total_batches = (total + BATCH_SIZE - 1) // BATCH_SIZE
|
| 75 |
+
|
| 76 |
+
# Nomic requires "search_document:" prefix for document embeddings
|
| 77 |
+
texts = [
|
| 78 |
+
f"search_document: {c.get('chunk_text', '') or c.get('scheme_title', '')}"
|
| 79 |
+
for c in batch
|
| 80 |
+
]
|
| 81 |
+
|
| 82 |
+
print(f"π Batch {batch_num}/{total_batches} ({len(batch)} rows)...")
|
| 83 |
+
|
| 84 |
+
with torch.inference_mode():
|
| 85 |
+
embeddings = model.encode(
|
| 86 |
+
texts,
|
| 87 |
+
normalize_embeddings=True,
|
| 88 |
+
show_progress_bar=False,
|
| 89 |
+
batch_size=32
|
| 90 |
+
).tolist()
|
| 91 |
+
|
| 92 |
+
# UPSERT each row's embedding_v2
|
| 93 |
+
for chunk, embedding in zip(batch, embeddings):
|
| 94 |
+
supabase.table('document_chunks') \
|
| 95 |
+
.update({'embedding_v2': embedding}) \
|
| 96 |
+
.eq('id', chunk['id']) \
|
| 97 |
+
.execute()
|
| 98 |
+
|
| 99 |
+
print(f"β
Batch {batch_num}/{total_batches} complete")
|
| 100 |
+
time.sleep(SLEEP_BETWEEN)
|
| 101 |
+
|
| 102 |
+
print(f"\nπ document_chunks backfill complete: {total} rows updated")
|
| 103 |
+
|
| 104 |
+
# ββ Phase 2B: Backfill query_cache.query_embedding_v2 ββββ
|
| 105 |
+
print("\nβββ PHASE 2B: Backfilling query_cache.query_embedding_v2 βββ")
|
| 106 |
+
|
| 107 |
+
cache_result = supabase.table('query_cache') \
|
| 108 |
+
.select('id, query_text') \
|
| 109 |
+
.is_('query_embedding_v2', 'null') \
|
| 110 |
+
.limit(MAX_ROWS) \
|
| 111 |
+
.execute()
|
| 112 |
+
|
| 113 |
+
cache_rows = cache_result.data or []
|
| 114 |
+
cache_total = len(cache_rows)
|
| 115 |
+
|
| 116 |
+
if cache_total == 0:
|
| 117 |
+
print("π query_cache: All rows already have query_embedding_v2")
|
| 118 |
+
else:
|
| 119 |
+
print(f"Found {cache_total} cache entries to backfill")
|
| 120 |
+
|
| 121 |
+
for i in range(0, cache_total, BATCH_SIZE):
|
| 122 |
+
batch = cache_rows[i:i + BATCH_SIZE]
|
| 123 |
+
batch_num = (i // BATCH_SIZE) + 1
|
| 124 |
+
|
| 125 |
+
# Nomic requires "search_query:" prefix for query embeddings
|
| 126 |
+
texts = [
|
| 127 |
+
f"search_query: {c.get('query_text', '')}"
|
| 128 |
+
for c in batch
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
with torch.inference_mode():
|
| 132 |
+
embeddings = model.encode(
|
| 133 |
+
texts,
|
| 134 |
+
normalize_embeddings=True,
|
| 135 |
+
show_progress_bar=False,
|
| 136 |
+
batch_size=32
|
| 137 |
+
).tolist()
|
| 138 |
+
|
| 139 |
+
for row, embedding in zip(batch, embeddings):
|
| 140 |
+
supabase.table('query_cache') \
|
| 141 |
+
.update({'query_embedding_v2': embedding}) \
|
| 142 |
+
.eq('id', row['id']) \
|
| 143 |
+
.execute()
|
| 144 |
+
|
| 145 |
+
time.sleep(SLEEP_BETWEEN)
|
| 146 |
+
|
| 147 |
+
print(f"\nπ query_cache backfill complete: {cache_total} rows updated")
|
| 148 |
+
|
| 149 |
+
# ββ Summary ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 150 |
+
print("\n" + "β" * 60)
|
| 151 |
+
print("MIGRATION PHASE 2 COMPLETE")
|
| 152 |
+
print(f" document_chunks: {total} rows backfilled")
|
| 153 |
+
print(f" query_cache: {cache_total} rows backfilled")
|
| 154 |
+
print("β" * 60)
|
| 155 |
+
print("\nNEXT STEPS:")
|
| 156 |
+
print(" 1. Run migration 008 (concurrent index creation)")
|
| 157 |
+
print(" 2. Verify indexes are VALID")
|
| 158 |
+
print(" 3. Run migration 009 (contract β column swap)")
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if __name__ == '__main__':
|
| 162 |
+
main()
|