Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .gitattributes +4 -0
- rag_system/Try Docs/Constitution Of india.pdf +3 -0
- rag_system/Try Docs/Contract of Insurance.pdf +3 -0
- rag_system/Try Docs/Indian Penal Code.pdf +3 -0
- rag_system/Try Docs/Origin_of_Species.pdf +3 -0
- rag_system/api.py +86 -0
- rag_system/config.py +4 -0
- rag_system/vector_store.py +15 -1
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
rag_system/Try[[:space:]]Docs/Constitution[[:space:]]Of[[:space:]]india.pdf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
rag_system/Try[[:space:]]Docs/Contract[[:space:]]of[[:space:]]Insurance.pdf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
rag_system/Try[[:space:]]Docs/Indian[[:space:]]Penal[[:space:]]Code.pdf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
rag_system/Try[[:space:]]Docs/Origin_of_Species.pdf filter=lfs diff=lfs merge=lfs -text
|
rag_system/Try Docs/Constitution Of india.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:819ba7adc5e5ae4063f26c82472283ba682ddb662e5a7f864cf26984d99b26ed
|
| 3 |
+
size 2413611
|
rag_system/Try Docs/Contract of Insurance.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1e9bd70c489bb3e31d73f1df82644c7e0295b736e6177723a13a8e70d434ac3f
|
| 3 |
+
size 452255
|
rag_system/Try Docs/Indian Penal Code.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:038c736730c09d5b72b1642ab8056607ca546c0b87631811da1a30accd08f81d
|
| 3 |
+
size 1529218
|
rag_system/Try Docs/Origin_of_Species.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:07baf1c96315e54f042fbb152ac3a4449d70899dc7c2165e03ccf39126a74e20
|
| 3 |
+
size 613465
|
rag_system/api.py
CHANGED
|
@@ -21,6 +21,7 @@ from .vector_store import (
|
|
| 21 |
add_documents, load_or_create_store, is_loaded,
|
| 22 |
list_collections, get_collection_stats, delete_collection,
|
| 23 |
cleanup_stale_collections, get_collection_embedding_mode,
|
|
|
|
| 24 |
)
|
| 25 |
from .query_engine import query as run_query, stream_query, pipeline_stream_query
|
| 26 |
from .eval import evaluate
|
|
@@ -46,6 +47,9 @@ _ingest_jobs: dict[str, dict] = {}
|
|
| 46 |
# Raw file bytes for document preview: collection_name -> (bytes, content_type)
|
| 47 |
_doc_files: dict[str, tuple[bytes, str]] = {}
|
| 48 |
|
|
|
|
|
|
|
|
|
|
| 49 |
_FILE_CONTENT_TYPES: dict[str, str] = {
|
| 50 |
'.pdf': 'application/pdf',
|
| 51 |
'.txt': 'text/plain; charset=utf-8',
|
|
@@ -115,6 +119,41 @@ def _safe_coll_name(filename: str) -> str:
|
|
| 115 |
return safe or 'doc'
|
| 116 |
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
# Lifespan (startup / shutdown)
|
| 119 |
@asynccontextmanager
|
| 120 |
async def lifespan(app: FastAPI):
|
|
@@ -135,6 +174,8 @@ async def lifespan(app: FastAPI):
|
|
| 135 |
if d.is_dir():
|
| 136 |
load_or_create_store(d.name)
|
| 137 |
|
|
|
|
|
|
|
| 138 |
logger.info("RAG API ready!")
|
| 139 |
|
| 140 |
# Background session-cleanup loop: remove collections idle > 30 min
|
|
@@ -207,6 +248,43 @@ async def embeddings_info():
|
|
| 207 |
return get_embeddings_runtime_info()
|
| 208 |
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
# ββ Ingest ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 211 |
|
| 212 |
@app.post("/ingest", response_model=IngestResponse, tags=["ingest"])
|
|
@@ -503,6 +581,14 @@ async def get_document_raw(collection_name: str):
|
|
| 503 |
"""Serve raw document bytes for in-browser preview."""
|
| 504 |
from fastapi.responses import Response
|
| 505 |
entry = _doc_files.get(collection_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
if not entry:
|
| 507 |
raise HTTPException(status_code=404, detail=f"Document '{collection_name}' not available for preview")
|
| 508 |
data, media_type = entry
|
|
|
|
| 21 |
add_documents, load_or_create_store, is_loaded,
|
| 22 |
list_collections, get_collection_stats, delete_collection,
|
| 23 |
cleanup_stale_collections, get_collection_embedding_mode,
|
| 24 |
+
pin_collection,
|
| 25 |
)
|
| 26 |
from .query_engine import query as run_query, stream_query, pipeline_stream_query
|
| 27 |
from .eval import evaluate
|
|
|
|
| 47 |
# Raw file bytes for document preview: collection_name -> (bytes, content_type)
|
| 48 |
_doc_files: dict[str, tuple[bytes, str]] = {}
|
| 49 |
|
| 50 |
+
# Try Docs file paths: collection_name -> path
|
| 51 |
+
_try_doc_paths: dict[str, "Path"] = {}
|
| 52 |
+
|
| 53 |
_FILE_CONTENT_TYPES: dict[str, str] = {
|
| 54 |
'.pdf': 'application/pdf',
|
| 55 |
'.txt': 'text/plain; charset=utf-8',
|
|
|
|
| 119 |
return safe or 'doc'
|
| 120 |
|
| 121 |
|
| 122 |
+
def _try_doc_collection_name(filename: str) -> str:
|
| 123 |
+
return f"{settings.try_docs_prefix}{_safe_coll_name(filename)}"
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _load_try_docs() -> None:
|
| 127 |
+
"""Load pre-indexed Try Docs into memory and cache raw bytes for preview."""
|
| 128 |
+
from pathlib import Path
|
| 129 |
+
|
| 130 |
+
try_dir = Path(settings.try_docs_path)
|
| 131 |
+
if not try_dir.exists():
|
| 132 |
+
logger.info("Try Docs folder not found at %s", try_dir)
|
| 133 |
+
return
|
| 134 |
+
|
| 135 |
+
for path in sorted(try_dir.iterdir()):
|
| 136 |
+
if not path.is_file():
|
| 137 |
+
continue
|
| 138 |
+
suffix = path.suffix.lower()
|
| 139 |
+
if suffix not in _FILE_CONTENT_TYPES:
|
| 140 |
+
continue
|
| 141 |
+
|
| 142 |
+
collection = _try_doc_collection_name(path.name)
|
| 143 |
+
_try_doc_paths[collection] = path
|
| 144 |
+
|
| 145 |
+
store = load_or_create_store(collection)
|
| 146 |
+
if store is not None:
|
| 147 |
+
pin_collection(collection)
|
| 148 |
+
else:
|
| 149 |
+
logger.warning("Try Doc index missing for '%s' (%s)", path.name, collection)
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
_doc_files[collection] = (path.read_bytes(), _FILE_CONTENT_TYPES[suffix])
|
| 153 |
+
except Exception:
|
| 154 |
+
logger.warning("Failed to cache Try Doc file bytes for '%s'", path.name)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
# Lifespan (startup / shutdown)
|
| 158 |
@asynccontextmanager
|
| 159 |
async def lifespan(app: FastAPI):
|
|
|
|
| 174 |
if d.is_dir():
|
| 175 |
load_or_create_store(d.name)
|
| 176 |
|
| 177 |
+
_load_try_docs()
|
| 178 |
+
|
| 179 |
logger.info("RAG API ready!")
|
| 180 |
|
| 181 |
# Background session-cleanup loop: remove collections idle > 30 min
|
|
|
|
| 248 |
return get_embeddings_runtime_info()
|
| 249 |
|
| 250 |
|
| 251 |
+
# ββ Try Docs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 252 |
+
|
| 253 |
+
@app.get("/try_docs", tags=["try_docs"])
|
| 254 |
+
async def list_try_docs():
|
| 255 |
+
"""List pre-indexed Try Docs available to add to a session."""
|
| 256 |
+
from pathlib import Path
|
| 257 |
+
|
| 258 |
+
try_dir = Path(settings.try_docs_path)
|
| 259 |
+
if not try_dir.exists():
|
| 260 |
+
return {"docs": []}
|
| 261 |
+
|
| 262 |
+
docs = []
|
| 263 |
+
for path in sorted(try_dir.iterdir()):
|
| 264 |
+
if not path.is_file():
|
| 265 |
+
continue
|
| 266 |
+
suffix = path.suffix.lower()
|
| 267 |
+
if suffix not in _FILE_CONTENT_TYPES:
|
| 268 |
+
continue
|
| 269 |
+
|
| 270 |
+
collection = _try_doc_collection_name(path.name)
|
| 271 |
+
_try_doc_paths.setdefault(collection, path)
|
| 272 |
+
|
| 273 |
+
index_path = Path(settings.faiss_index_path) / collection
|
| 274 |
+
stats = get_collection_stats(collection) if index_path.exists() else None
|
| 275 |
+
|
| 276 |
+
docs.append({
|
| 277 |
+
"filename": path.name,
|
| 278 |
+
"collection": collection,
|
| 279 |
+
"chunks": stats["chunk_count"] if stats else 0,
|
| 280 |
+
"embedding_mode": stats["embedding_mode"] if stats else None,
|
| 281 |
+
"size_mb": stats["size_mb"] if stats else 0.0,
|
| 282 |
+
"ready": stats is not None,
|
| 283 |
+
})
|
| 284 |
+
|
| 285 |
+
return {"docs": docs}
|
| 286 |
+
|
| 287 |
+
|
| 288 |
# ββ Ingest ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 289 |
|
| 290 |
@app.post("/ingest", response_model=IngestResponse, tags=["ingest"])
|
|
|
|
| 581 |
"""Serve raw document bytes for in-browser preview."""
|
| 582 |
from fastapi.responses import Response
|
| 583 |
entry = _doc_files.get(collection_name)
|
| 584 |
+
if not entry and collection_name in _try_doc_paths:
|
| 585 |
+
path = _try_doc_paths[collection_name]
|
| 586 |
+
try:
|
| 587 |
+
suffix = path.suffix.lower()
|
| 588 |
+
_doc_files[collection_name] = (path.read_bytes(), _FILE_CONTENT_TYPES.get(suffix, 'application/octet-stream'))
|
| 589 |
+
entry = _doc_files.get(collection_name)
|
| 590 |
+
except Exception:
|
| 591 |
+
entry = None
|
| 592 |
if not entry:
|
| 593 |
raise HTTPException(status_code=404, detail=f"Document '{collection_name}' not available for preview")
|
| 594 |
data, media_type = entry
|
rag_system/config.py
CHANGED
|
@@ -20,6 +20,10 @@ class Settings(BaseSettings):
|
|
| 20 |
embedding_batch_size: int = 32
|
| 21 |
embedding_normalize: bool = True
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
#FAISS
|
| 24 |
faiss_index_path: str = "./faiss_indexes"
|
| 25 |
faiss_index_name: str = "prod_rag"
|
|
|
|
| 20 |
embedding_batch_size: int = 32
|
| 21 |
embedding_normalize: bool = True
|
| 22 |
|
| 23 |
+
# Try Docs (pre-indexed demo docs)
|
| 24 |
+
try_docs_path: str = os.path.join(os.path.dirname(__file__), "Try Docs")
|
| 25 |
+
try_docs_prefix: str = "try__"
|
| 26 |
+
|
| 27 |
#FAISS
|
| 28 |
faiss_index_path: str = "./faiss_indexes"
|
| 29 |
faiss_index_name: str = "prod_rag"
|
rag_system/vector_store.py
CHANGED
|
@@ -26,6 +26,7 @@ _stores: dict[str, FAISS] = {}
|
|
| 26 |
# Tracks last-used timestamp per collection (epoch seconds) for TTL-based cleanup
|
| 27 |
_last_used: dict[str, float] = {}
|
| 28 |
_collection_embeddings: dict[str, str] = {}
|
|
|
|
| 29 |
|
| 30 |
_EMBEDDING_META_FILE = "embedding.json"
|
| 31 |
|
|
@@ -64,6 +65,14 @@ def get_collection_embedding_mode(collection: str) -> Optional[str]:
|
|
| 64 |
return None
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
def resolve_embedding_mode_for_collections(
|
| 68 |
collections: list[str],
|
| 69 |
requested_mode: Optional[str] = None,
|
|
@@ -274,7 +283,10 @@ def cleanup_stale_collections(ttl_seconds: int = 1800) -> list[str]:
|
|
| 274 |
Returns the list of collection names that were removed.
|
| 275 |
"""
|
| 276 |
cutoff = time.time() - ttl_seconds
|
| 277 |
-
stale = [
|
|
|
|
|
|
|
|
|
|
| 278 |
for name in stale:
|
| 279 |
logger.info(f"Cleaning up stale collection '{name}' (idle > {ttl_seconds}s)")
|
| 280 |
delete_collection(name)
|
|
@@ -290,6 +302,8 @@ def delete_collection(collection: str) -> bool:
|
|
| 290 |
del _stores[collection]
|
| 291 |
if collection in _collection_embeddings:
|
| 292 |
del _collection_embeddings[collection]
|
|
|
|
|
|
|
| 293 |
|
| 294 |
# Local import to avoid circular dependency with retriever
|
| 295 |
from .retriever import _bm25_cache
|
|
|
|
| 26 |
# Tracks last-used timestamp per collection (epoch seconds) for TTL-based cleanup
|
| 27 |
_last_used: dict[str, float] = {}
|
| 28 |
_collection_embeddings: dict[str, str] = {}
|
| 29 |
+
_pinned: set[str] = set()
|
| 30 |
|
| 31 |
_EMBEDDING_META_FILE = "embedding.json"
|
| 32 |
|
|
|
|
| 65 |
return None
|
| 66 |
|
| 67 |
|
| 68 |
+
def pin_collection(collection: str) -> None:
|
| 69 |
+
_pinned.add(collection)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def is_pinned_collection(collection: str) -> bool:
|
| 73 |
+
return collection in _pinned
|
| 74 |
+
|
| 75 |
+
|
| 76 |
def resolve_embedding_mode_for_collections(
|
| 77 |
collections: list[str],
|
| 78 |
requested_mode: Optional[str] = None,
|
|
|
|
| 283 |
Returns the list of collection names that were removed.
|
| 284 |
"""
|
| 285 |
cutoff = time.time() - ttl_seconds
|
| 286 |
+
stale = [
|
| 287 |
+
name for name, ts in list(_last_used.items())
|
| 288 |
+
if ts < cutoff and name not in _pinned
|
| 289 |
+
]
|
| 290 |
for name in stale:
|
| 291 |
logger.info(f"Cleaning up stale collection '{name}' (idle > {ttl_seconds}s)")
|
| 292 |
delete_collection(name)
|
|
|
|
| 302 |
del _stores[collection]
|
| 303 |
if collection in _collection_embeddings:
|
| 304 |
del _collection_embeddings[collection]
|
| 305 |
+
if collection in _pinned:
|
| 306 |
+
_pinned.discard(collection)
|
| 307 |
|
| 308 |
# Local import to avoid circular dependency with retriever
|
| 309 |
from .retriever import _bm25_cache
|