Spaces:
Runtime error
Runtime error
| import os | |
| import sys | |
| import pickle | |
| import numpy as np | |
| import faiss | |
| from sentence_transformers import SentenceTransformer | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from config import ( | |
| KNOWLEDGE_DIR, VECTORSTORE_DIR, | |
| EMBEDDING_MODEL, CHUNK_SIZE, CHUNK_OVERLAP, TOP_K_RETRIEVAL, | |
| ) | |
| os.makedirs(VECTORSTORE_DIR, exist_ok=True) | |
| INDEX_PATH = os.path.join(VECTORSTORE_DIR, "faiss.index") | |
| CHUNKS_PATH = os.path.join(VECTORSTORE_DIR, "chunks.pkl") | |
| _embedder = None | |
| _index = None | |
| _chunks = None | |
| def _get_embedder(): | |
| global _embedder | |
| if _embedder is None: | |
| _embedder = SentenceTransformer(EMBEDDING_MODEL) | |
| return _embedder | |
| # ββ Chunking βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _chunk_text(text: str, source: str) -> list[dict]: | |
| words = text.split() | |
| chunks = [] | |
| step = CHUNK_SIZE - CHUNK_OVERLAP | |
| for i in range(0, len(words), step): | |
| chunk_words = words[i: i + CHUNK_SIZE] | |
| if len(chunk_words) < 20: | |
| continue | |
| chunks.append({"text": " ".join(chunk_words), "source": source}) | |
| return chunks | |
| # ββ Build vectorstore ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_vectorstore(force: bool = False): | |
| if not force and os.path.exists(INDEX_PATH) and os.path.exists(CHUNKS_PATH): | |
| print("[RAG] Vectorstore already exists. Skipping build.") | |
| return | |
| embedder = _get_embedder() | |
| all_chunks = [] | |
| print("[RAG] Loading knowledge docs...") | |
| for fname in sorted(os.listdir(KNOWLEDGE_DIR)): | |
| if not fname.endswith(".txt"): | |
| continue | |
| fpath = os.path.join(KNOWLEDGE_DIR, fname) | |
| with open(fpath, "r", encoding="utf-8") as f: | |
| text = f.read() | |
| chunks = _chunk_text(text, source=fname.replace(".txt", "")) | |
| all_chunks.extend(chunks) | |
| print(f" {fname}: {len(chunks)} chunks") | |
| print(f"\n[RAG] Total chunks: {len(all_chunks)}") | |
| print("[RAG] Encoding chunks...") | |
| texts = [c["text"] for c in all_chunks] | |
| embeddings = embedder.encode(texts, show_progress_bar=True, batch_size=32) | |
| embeddings = embeddings.astype(np.float32) | |
| # Normalize for cosine similarity | |
| faiss.normalize_L2(embeddings) | |
| dim = embeddings.shape[1] | |
| index = faiss.IndexFlatIP(dim) # Inner product = cosine after normalization | |
| index.add(embeddings) | |
| faiss.write_index(index, INDEX_PATH) | |
| with open(CHUNKS_PATH, "wb") as f: | |
| pickle.dump(all_chunks, f) | |
| print(f"[RAG] Vectorstore saved: {index.ntotal} vectors dim={dim}") | |
| # ββ Load vectorstore βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _load_vectorstore(): | |
| global _index, _chunks | |
| if _index is None: | |
| if not os.path.exists(INDEX_PATH): | |
| raise FileNotFoundError("Vectorstore not found. Run build_vectorstore() first.") | |
| _index = faiss.read_index(INDEX_PATH) | |
| with open(CHUNKS_PATH, "rb") as f: | |
| _chunks = pickle.load(f) | |
| # ββ Retrieve βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def retrieve(query: str, top_k: int = TOP_K_RETRIEVAL) -> list[dict]: | |
| _load_vectorstore() | |
| embedder = _get_embedder() | |
| q_embed = embedder.encode([query]).astype(np.float32) | |
| faiss.normalize_L2(q_embed) | |
| scores, idxs = _index.search(q_embed, top_k) | |
| results = [] | |
| for score, idx in zip(scores[0], idxs[0]): | |
| if idx == -1: | |
| continue | |
| chunk = _chunks[idx].copy() | |
| chunk["score"] = round(float(score), 4) | |
| results.append(chunk) | |
| return results | |
| # ββ CLI test βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| build_vectorstore(force=True) | |
| print("\n[RAG] Test retrieval: 'melanoma ABCDE dermoscopy'") | |
| results = retrieve("melanoma ABCDE dermoscopy signs") | |
| for i, r in enumerate(results, 1): | |
| print(f"\n [{i}] source={r['source']} score={r['score']}") | |
| print(f" {r['text'][:200]}...") | |