feat(embeddings): cache persistent de vectori in SQLite + warmup in fundal
Vectorii corpusului k-NN persista in tabela embedding_cache (PK model+text_hash,
blob float32 LE); la warmup se vectorizeaza doar textele lipsa din cache, deci
restartul cu corpus neschimbat nu mai plateste ~1-2 min de embed (embed=0).
- app/embedding_cache.py: serializare array('f'), load/save/purge chunk 500 cu
BEGIN/COMMIT explicit (conexiuni autocommit), validare dimensiune la scriere
si citire, orchestrare sync_corpus_vectors cu embed_fn injectat
- index_corpus(vectors=): vectori precalculati cu validare aliniere; mismatch
-> fallback embed complet
- ensure_embeddings_corpus: warmup in thread la startup (block=True), calea de
request ne-blocanta (acquire non-blocking pe lock; warmup in curs -> return
imediat); purjare orfane + modele vechi doar dupa indexare reusita
- log warmup: cache=N embed=M in Xs
- 31 teste noi (cold/warm/incremental, model schimbat, concurenta, ranking
exact, echivalenta float32); suita completa 1596 passed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
22
app/main.py
22
app/main.py
@@ -7,6 +7,7 @@ un worker mort nu trebuie sa lase containerul "sanatos".
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
import threading
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
@@ -39,6 +40,25 @@ from .web.csrf import CsrfError
|
||||
from .web.session import AdminRequired, LoginRequired
|
||||
|
||||
|
||||
def _warmup_embeddings() -> None:
|
||||
"""Incarca modelul de embeddings si indexeaza corpusul SILVER, in fundal.
|
||||
|
||||
Ruleaza intr-un thread daemon la startup: incarcarea modelului (~230MB) plus
|
||||
vectorizarea corpusului dureaza zeci de secunde si NU are voie sa blocheze
|
||||
primul request pe /mapari. Pana termina, sugestiile embeddings lipsesc
|
||||
(degradare gratioasa); GOLD/SILVER/fuzzy functioneaza normal.
|
||||
"""
|
||||
from .mapping import ensure_embeddings_corpus
|
||||
try:
|
||||
conn = get_connection()
|
||||
try:
|
||||
ensure_embeddings_corpus(conn, block=True)
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass # best-effort: esecul warmup-ului nu opreste API-ul
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
install_log_redaction()
|
||||
@@ -49,6 +69,8 @@ async def lifespan(app: FastAPI):
|
||||
# cheia API si secretul de sesiune, in loc de o instanta descoperita post-deploy.
|
||||
validate_prod_invariants(get_settings())
|
||||
init_db()
|
||||
if get_settings().embeddings_enabled:
|
||||
threading.Thread(target=_warmup_embeddings, name="emb-warmup", daemon=True).start()
|
||||
yield
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user