CoolFace
Apppublic

Mister2005/Cross-Encoder-Reranking-API

sourceHugging Faceupdated 1mo agoView on Hugging Face
1likes
app.py163 linesDownload Raw Back to root
1"""2FinReg BGE Cross-Encoder Reranking API.3Hosted on Hugging Face Spaces (Mister2005/Cross-Encoder-Reranking-API).4 5Default Model: BAAI/bge-reranker-base (State-of-the-art Chinese & English Cross-Encoder)6Framework: FastAPI + SentenceTransformers / PyTorch CPU/GPU7"""8 9import os10import time11import torch12import uvicorn13from typing import List, Dict, Any, Optional14from fastapi import FastAPI, HTTPException15from fastapi.responses import HTMLResponse16from pydantic import BaseModel, Field17from sentence_transformers import CrossEncoder18 19MODEL_NAME = os.getenv("MODEL_NAME", "BAAI/bge-reranker-base")20DEVICE = "cuda" if torch.cuda.is_available() else "cpu"21 22app = FastAPI(23    title="FinReg BGE Reranker API",24    description="High-Precision Regulatory Document Re-Ranking powered by BAAI/bge-reranker-base.",25    version="2.0.0"26)27 28print(f"Loading CrossEncoder model '{MODEL_NAME}' on {DEVICE}...")29try:30    model = CrossEncoder(MODEL_NAME, max_length=512, device=DEVICE)31    print(f"Successfully initialized {MODEL_NAME}!")32except Exception as e:33    print(f"Error loading model: {e}")34    model = None35 36class RerankItem(BaseModel):37    rank: int38    original_index: int39    score: float40    document: str41 42class RerankRequest(BaseModel):43    query: str = Field(..., description="The search query or compliance question")44    documents: List[str] = Field(..., description="List of candidate text passages to re-rank")45    top_k: Optional[int] = Field(default=None, description="Number of top passages to return (defaults to all)")46 47class RerankResponse(BaseModel):48    query: str49    model: str50    total_evaluated: int51    latency_ms: float52    scores: List[float]53    ranked_indices: List[int]54    ranked_results: List[RerankItem]55 56@app.get("/", response_class=HTMLResponse)57def root_ui():58    return f"""59    <!DOCTYPE html>60    <html>61    <head>62        <title>FinReg BGE Reranker API</title>63        <meta charset="utf-8">64        <style>65            body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #1e293b; background: #f8fafc; }}66            .card {{ background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); }}67            h1 {{ color: #0f172a; margin-top: 0; }}68            .badge {{ display: inline-block; padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; background: #e0e7ff; color: #3730a3; }}69            .endpoint {{ background: #f1f5f9; padding: 12px; border-radius: 6px; font-family: monospace; margin: 12px 0; }}70            a.btn {{ display: inline-block; background: #2563eb; color: white; padding: 10px 18px; border-radius: 6px; text-decoration: none; font-weight: 500; margin-top: 15px; }}71            a.btn:hover {{ background: #1d4ed8; }}72        </style>73    </head>74    <body>75        <div class="card">76            <span class="badge">Active Microservice</span>77            <h1>⚖️ FinReg BGE Reranker API</h1>78            <p>Cloud cross-encoder service powering statutory retrieval re-ranking for Indian Regulatory Compliance.</p>79            80            <p><strong>Active Model:</strong> <code>{MODEL_NAME}</code> ({DEVICE.upper()})</p>81            <p><strong>Status:</strong> {'🟢 Online' if model else '🔴 Loading Error'}</p>82            83            <h3>API Endpoints:</h3>84            <div class="endpoint">POST /rerank (Standard JSON Payload)</div>85            <div class="endpoint">GET /health (Healthcheck)</div>86            <div class="endpoint">GET /docs (Interactive Swagger API Explorer)</div>87            88            <a class="btn" href="/docs">Open Interactive API Docs (Swagger) &rarr;</a>89        </div>90    </body>91    </html>92    """93 94@app.get("/health")95def health_check():96    return {97        "status": "healthy" if model else "unhealthy",98        "model": MODEL_NAME,99        "device": DEVICE,100        "model_loaded": model is not None101    }102 103@app.post("/rerank", response_model=RerankResponse)104def rerank_documents(request: RerankRequest):105    if not model:106        raise HTTPException(status_code=503, detail="Model is not loaded on server.")107    108    if not request.query.strip() or not request.documents:109        return RerankResponse(110            query=request.query,111            model=MODEL_NAME,112            total_evaluated=0,113            latency_ms=0.0,114            scores=[],115            ranked_indices=[],116            ranked_results=[]117        )118 119    start_time = time.time()120    try:121        # Create (query, doc) pairs122        pairs = [[request.query, doc] for doc in request.documents]123        124        # CrossEncoder scoring with sigmoid activation125        raw_scores = model.predict(pairs, convert_to_numpy=True, show_progress_bar=False)126        scores_list = [float(s) for s in raw_scores]127        128        # Sigmoid normalization: 1 / (1 + exp(-score))129        probs = [round(float(torch.sigmoid(torch.tensor(s)).item()), 4) for s in scores_list]130 131        # Rank pairs132        indexed = list(enumerate(probs))133        indexed.sort(key=lambda x: x[1], reverse=True)134 135        ranked_indices = [idx for idx, _ in indexed]136        137        top_k = request.top_k if request.top_k and request.top_k > 0 else len(request.documents)138        ranked_results = []139        for rank_num, (orig_idx, score) in enumerate(indexed[:top_k], 1):140            ranked_results.append(RerankItem(141                rank=rank_num,142                original_index=orig_idx,143                score=score,144                document=request.documents[orig_idx]145            ))146 147        latency = (time.time() - start_time) * 1000.0148 149        return RerankResponse(150            query=request.query,151            model=MODEL_NAME,152            total_evaluated=len(request.documents),153            latency_ms=round(latency, 2),154            scores=probs,155            ranked_indices=ranked_indices,156            ranked_results=ranked_results157        )158    except Exception as e:159        raise HTTPException(status_code=500, detail=str(e))160 161if __name__ == "__main__":162    uvicorn.run(app, host="0.0.0.0", port=7860)163