acl-api / app /main.py
ivykopal's picture
fix: correct FAISS id mapping across syncs, atomic snapshot upload, startup logging, health timestamp, real anthology field access
03b3d27
Raw
History Blame Contribute Delete
2.73 kB
"""FastAPI app exposing keyword and similarity search over the ACL Anthology corpus."""
import logging
import os
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from app.config import get_settings
from app.schemas import PaperResult, SimilarityResult, HealthResponse
from common.db import init_db
from common.vector_index import load_index
from sync.embeddings_client import EmbeddingsClient
from sync.hf_dataset_store import download_snapshot
from app.search_service import SearchService
logger = logging.getLogger(__name__)
app = FastAPI(title="ACL Anthology Search API")
app.state.startup_ok = False
app.state.search_service = None
app.state.last_synced_at = None
@app.on_event("startup")
def load_snapshot() -> None:
settings = get_settings()
try:
state = download_snapshot(settings.hf_repo_id, settings.data_dir, settings.hf_token)
conn = init_db(os.path.join(settings.data_dir, "papers.db"))
index = load_index(os.path.join(settings.data_dir, "index.faiss"))
embeddings_client = EmbeddingsClient(base_url=settings.embedding_base_url, api_key=settings.embedding_api_key)
app.state.search_service = SearchService(conn=conn, index=index, embeddings_client=embeddings_client)
app.state.last_synced_at = state.get("last_synced_at")
app.state.startup_ok = True
except Exception:
logger.exception("Failed to load index/DB snapshot on startup")
app.state.startup_ok = False
@app.middleware("http")
async def block_when_not_ready(request, call_next):
if not app.state.startup_ok and request.url.path != "/health":
return JSONResponse(status_code=503, content={"detail": "service not ready"})
return await call_next(request)
@app.get("/health", response_model=HealthResponse)
def health():
if not app.state.startup_ok:
raise HTTPException(status_code=503, detail="index/db not loaded")
return HealthResponse(
status="ok",
index_size=app.state.search_service.index.ntotal,
last_synced_at=getattr(app.state, "last_synced_at", None),
)
@app.get("/search/keyword", response_model=list[PaperResult])
def search_keyword(q: str, limit: int = 10):
return app.state.search_service.keyword_search(q, limit=limit)
@app.get("/search/similarity", response_model=list[SimilarityResult])
def search_similarity(q: str, k: int = 10):
return app.state.search_service.similarity_search(q, k=k)
@app.get("/paper/{paper_id}", response_model=PaperResult)
def get_paper(paper_id: str):
paper = app.state.search_service.get_paper(paper_id)
if paper is None:
raise HTTPException(status_code=404, detail="paper not found")
return paper