Spaces:
Sleeping
Sleeping
File size: 8,034 Bytes
1171cba 649d2d5 1171cba 649d2d5 1171cba a037cf8 41018f6 b1de6d2 41018f6 7715973 41018f6 64fd9b7 41018f6 7715973 a7ef914 40a908e 78bd110 41018f6 b1de6d2 78bd110 8ea6d3a 78bd110 41018f6 78bd110 8ea6d3a 78bd110 41018f6 8ea6d3a a037cf8 41018f6 a037cf8 41018f6 a037cf8 41018f6 a037cf8 41018f6 7715973 a037cf8 41018f6 7715973 8ea6d3a 40a908e b1de6d2 41018f6 b1de6d2 41018f6 b1de6d2 40a908e a7ef914 41018f6 8ea6d3a 41018f6 a7ef914 41018f6 8ea6d3a 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 649d2d5 |
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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
from app.storage import DATA_DIR, INDEX_DIR, HISTORY_JSON
from app.storage import DATA_DIR, INDEX_DIR, HISTORY_JSON
# app/api.py
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))
@app.on_event("startup")
async def _ensure_dirs():
try:
INDEX_DIR.mkdir(parents=True, exist_ok=True)
# HISTORY_JSON parent is DATA_DIR
HISTORY_JSON.parent.mkdir(parents=True, exist_ok=True)
except Exception:
# boot-un dayanmasının qarşısını alaq
pass
|