Spaces:
Sleeping
Sleeping
Download app.py from phamluan/namboqa-api: direct link, hf CLI and curl.
- Browser
- Download file 4.39 kB
-
https://huggingface.co/spaces/phamluan/namboqa-api/resolve/main/app.py
- Command line
-
hf download hf://spaces/phamluan/namboqa-api/app.py
-
curl -L -o app.py https://huggingface.co/spaces/phamluan/namboqa-api/resolve/main/app.py
4.39 kB
| # -*- coding: utf-8 -*- | |
| """ | |
| API demo NamBoQA — chạy trên Hugging Face Spaces (Docker, cổng 7860). | |
| GET /health trạng thái từng thành phần (BM25, bi-encoder, reader...) | |
| GET /stats thống kê bộ dữ liệu + mô hình đang dùng | |
| GET /samples câu hỏi mẫu (mặc định lấy từ tập test) | |
| POST /ask hỏi: truy hồi top-k rồi đọc hiểu | |
| Chạy local: | |
| pip install -r requirements.txt | |
| uvicorn app:app --reload --port 7860 | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| import threading | |
| import time | |
| from collections import OrderedDict, defaultdict, deque | |
| from typing import Literal, Optional | |
| from fastapi import Depends, FastAPI, Header, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from engine import Engine | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", datefmt="%H:%M:%S") | |
| log = logging.getLogger("namboqa") | |
| engine = Engine(os.environ.get("DATA_DIR", "data")) | |
| app = FastAPI(title="NamBoQA demo API", version="1.0", | |
| description="Hỏi–đáp du lịch và di sản văn hoá Nam Bộ (RAG hai giai đoạn)") | |
| origins = [o.strip() for o in os.environ.get("ALLOWED_ORIGINS", "*").split(",") if o.strip()] | |
| app.add_middleware(CORSMiddleware, allow_origins=origins, allow_methods=["GET", "POST"], | |
| allow_headers=["*"]) | |
| def _startup(): | |
| engine.load_fast() # dữ liệu + BM25: vài giây | |
| threading.Thread(target=engine.load_heavy, daemon=True).start() # mô hình: chạy nền | |
| # ------------------------------------------------------------------ bảo vệ | |
| API_KEY = os.environ.get("DEMO_API_KEY") # đặt thì mọi request phải gửi X-API-Key | |
| RATE = int(os.environ.get("RATE_LIMIT_PER_MIN", "20")) | |
| _hits: dict[str, deque] = defaultdict(deque) | |
| def guard(request: Request, x_api_key: Optional[str] = Header(default=None)): | |
| if API_KEY and x_api_key != API_KEY: | |
| raise HTTPException(401, "Sai hoặc thiếu X-API-Key") | |
| # proxy trên Vercel chuyển IP người dùng qua x-client-ip; gọi thẳng thì dùng x-forwarded-for | |
| ip = (request.headers.get("x-client-ip") or request.headers.get("x-forwarded-for") | |
| or (request.client.host if request.client else "?")) | |
| ip = ip.split(",")[0].strip() | |
| now, q = time.time(), _hits[ip] | |
| while q and now - q[0] > 60: | |
| q.popleft() | |
| if len(q) >= RATE: | |
| raise HTTPException(429, f"Quá {RATE} câu hỏi/phút, thử lại sau ít giây") | |
| q.append(now) | |
| # ------------------------------------------------------------------ cache | |
| _cache: OrderedDict = OrderedDict() | |
| CACHE_SIZE = 256 | |
| class AskIn(BaseModel): | |
| question: str = Field(..., min_length=3, max_length=500) | |
| retriever: Literal["bm25", "dense", "hybrid"] = "hybrid" | |
| reader: Literal["extractive", "generative", "none"] = "generative" | |
| top_k: int = Field(5, ge=1, le=10) | |
| alpha: Optional[float] = Field(None, ge=0.0, le=1.0) | |
| def root(): | |
| return {"name": "NamBoQA demo API", "docs": "/docs", "health": "/health"} | |
| def health(): | |
| return {"status": engine.status, "errors": engine.errors, | |
| "ready": engine.status["bm25"] == "ready"} | |
| def stats(): | |
| return engine.stats() | |
| def samples(n: int = 6, type: Optional[str] = None, split: str = "test", seed: Optional[int] = None): | |
| return engine.samples(min(n, 30), type, split, seed) | |
| def ask(body: AskIn): | |
| key = (body.question.strip(), body.retriever, body.reader, body.top_k, body.alpha, | |
| engine.status["dense"], engine.status["extractive"]) | |
| if key in _cache: | |
| _cache.move_to_end(key) | |
| return {**_cache[key], "cached": True} | |
| try: | |
| res = engine.ask(body.question.strip(), body.retriever, body.reader, body.top_k, body.alpha) | |
| except RuntimeError as e: | |
| raise HTTPException(503, str(e)) | |
| except Exception as e: | |
| log.exception("lỗi khi trả lời") | |
| raise HTTPException(500, f"Lỗi back-end: {str(e)[:200]}") | |
| _cache[key] = res | |
| if len(_cache) > CACHE_SIZE: | |
| _cache.popitem(last=False) | |
| return res | |