Spaces:
Sleeping
Sleeping
File size: 6,836 Bytes
64fd9b7 7715973 a037cf8 41018f6 b1de6d2 41018f6 7715973 41018f6 64fd9b7 41018f6 7715973 a7ef914 40a908e 78bd110 41018f6 b1de6d2 78bd110 41018f6 78bd110 41018f6 78bd110 41018f6 78bd110 41018f6 a037cf8 41018f6 a037cf8 41018f6 a037cf8 41018f6 a037cf8 41018f6 7715973 a037cf8 41018f6 7715973 41018f6 40a908e b1de6d2 41018f6 b1de6d2 41018f6 b1de6d2 40a908e a7ef914 41018f6 a7ef914 41018f6 a7ef914 78bd110 a7ef914 41018f6 a7ef914 64fd9b7 41018f6 7715973 41018f6 7715973 41018f6 64fd9b7 41018f6 7715973 41018f6 7715973 41018f6 7715973 41018f6 7715973 41018f6 7715973 41018f6 64fd9b7 a037cf8 41018f6 40a908e 41018f6 40a908e 41018f6 40a908e 41018f6 40a908e 41018f6 40a908e 26ad320 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# app/api.py
from __future__ import annotations
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List
import faiss
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from pydantic import BaseModel, Field
from .rag_system import SimpleRAG, UPLOAD_DIR, INDEX_DIR
__version__ = "1.3.2"
app = FastAPI(title="RAG API", version=__version__)
# βββββββββββββββββββββββββ CORS βββββββββββββββββββββββββ
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # tighten if needed
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ββββββββββββββββββββ Core singleton & metrics ββββββββββββββββββββ
rag = SimpleRAG()
METRICS: Dict[str, Any] = {
"questions_answered": 0,
"avg_ms": 0.0,
"last7_questions": [5, 8, 12, 7, 15, 11, 9], # placeholder sample
"last_added_chunks": 0,
}
HISTORY: List[Dict[str, Any]] = [] # [{"question":..., "timestamp":...}]
# βββββββββββββββββββββββββ Models βββββββββββββββββββββββββ
class UploadResponse(BaseModel):
message: str
filename: str
chunks_added: int
total_chunks: int
class AskRequest(BaseModel):
question: str = Field(min_length=3)
top_k: int = 5
# Optional routing hint: "all" (default) or "last"
scope: str = Field(default="all", pattern="^(all|last)$")
class AskResponse(BaseModel):
answer: str
contexts: List[str]
used_top_k: int
class HistoryResponse(BaseModel):
total_chunks: int
history: List[Dict[str, Any]]
# βββββββββββββββββββββββββ Routes βββββββββββββββββββββββββ
@app.get("/")
def root():
return RedirectResponse(url="/docs")
@app.get("/health")
def health():
return {
"status": "ok",
"version": __version__,
"summarizer": "extractive_en + translate + keyword_fallback",
"faiss_ntotal": getattr(rag.index, "ntotal", 0),
"model_dim": getattr(rag, "embed_dim", None),
}
@app.get("/debug/translate")
def debug_translate():
"""
Simple smoke test for the AZβEN translator pipeline (if available).
"""
try:
from transformers import pipeline # type: ignore
tr = pipeline(
"translation",
model="Helsinki-NLP/opus-mt-az-en",
cache_dir=str(rag.cache_dir),
device=-1,
)
out = tr("SΙnΙd tΙmiri vΙ quraΕdΔ±rΔ±lmasΔ± ilΙ baΔlΔ± iΕlΙr gΓΆrΓΌlΓΌb.", max_length=80)[0]["translation_text"]
return {"ok": True, "example_out": out}
except Exception as e:
return {"ok": False, "error": str(e)}
@app.post("/upload_pdf", response_model=UploadResponse)
def upload_pdf(file: UploadFile = File(...)):
"""
Accepts a PDF, extracts text, embeds, and adds to FAISS index.
"""
name = file.filename or "uploaded.pdf"
if not name.lower().endswith(".pdf"):
raise HTTPException(status_code=400, detail="Only .pdf files are accepted.")
dest = UPLOAD_DIR / name
try:
# Save whole file to disk
data = file.file.read()
if not data:
raise HTTPException(status_code=400, detail="Empty file.")
dest.write_bytes(data)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save PDF: {e}")
try:
added = rag.add_pdf(dest)
if added == 0:
raise HTTPException(status_code=400, detail="No extractable text found (likely a scanned PDF).")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Indexing failed: {e}")
METRICS["last_added_chunks"] = int(added)
return UploadResponse(
message="indexed",
filename=name,
chunks_added=added,
total_chunks=len(rag.chunks),
)
@app.post("/ask_question", response_model=AskResponse)
def ask_question(req: AskRequest):
"""
Retrieves top_k contexts and synthesizes an extractive answer.
Supports optional scope hint: "all" or "last".
"""
q = (req.question or "").strip()
if len(q) < 3:
raise HTTPException(status_code=400, detail="Question is too short.")
start = time.perf_counter()
# Prefer calling with scope if rag_system supports it; otherwise fallback.
try:
pairs = rag.search(q, k=req.top_k, scope=req.scope) # type: ignore[arg-type]
except TypeError:
pairs = rag.search(q, k=req.top_k)
contexts = [t for (t, _) in pairs]
answer = rag.synthesize_answer(q, contexts, max_sentences=4)
# metrics
elapsed_ms = (time.perf_counter() - start) * 1000.0
METRICS["questions_answered"] += 1
n = METRICS["questions_answered"]
METRICS["avg_ms"] = (METRICS["avg_ms"] * (n - 1) + elapsed_ms) / n
# history (cap to last 200)
HISTORY.append({
"question": q,
"timestamp": datetime.now(timezone.utc).isoformat(timespec="seconds"),
})
if len(HISTORY) > 200:
del HISTORY[: len(HISTORY) - 200]
return AskResponse(answer=answer, contexts=contexts, used_top_k=int(req.top_k))
@app.get("/get_history", response_model=HistoryResponse)
def get_history():
return {"total_chunks": len(rag.chunks), "history": HISTORY[-50:]}
@app.get("/stats")
def stats():
return {
"documents_indexed": len(list(UPLOAD_DIR.glob("*.pdf"))),
"questions_answered": METRICS["questions_answered"],
"avg_ms": round(float(METRICS["avg_ms"]), 2),
"last7_questions": METRICS.get("last7_questions", []),
"total_chunks": len(rag.chunks),
"faiss_ntotal": getattr(rag.index, "ntotal", 0),
"model_dim": getattr(rag, "embed_dim", None),
"last_added_chunks": METRICS.get("last_added_chunks", 0),
"version": __version__,
}
@app.post("/reset_index")
def reset_index():
try:
rag.index = faiss.IndexFlatIP(rag.embed_dim)
rag.chunks = []
rag.last_added = []
# remove persisted files if present
(INDEX_DIR / "faiss.index").unlink(missing_ok=True)
(INDEX_DIR / "meta.npy").unlink(missing_ok=True)
# persist empty state
rag._persist()
return {"message": "index reset", "ntotal": getattr(rag.index, "ntotal", 0)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
|