| import argparse |
| from pathlib import Path |
|
|
| import torch |
| from sentence_transformers import SentenceTransformer, util |
|
|
| DEFAULT_PICKLE = Path(__file__).parent / "rag_store_cpu.pkl" |
| DEFAULT_MODEL = "BAAI/bge-large-en-v1.5" |
| GOOGLE_DRIVE_FILE_ID = "1RLtLARA0G0v51CQckpQqfXeD10RSSA39" |
| GOOGLE_DRIVE_DOWNLOAD_URL = "https://drive.google.com/uc?export=download" |
|
|
|
|
| def get_device(): |
| if torch.cuda.is_available(): |
| return "cuda" |
|
|
| if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): |
| return "mps" |
|
|
| return "cpu" |
|
|
|
|
| def _get_google_drive_confirm_token(text): |
| import re |
|
|
| match = re.search(r"confirm=([0-9A-Za-z_\-]+)&", text) |
| if match: |
| return match.group(1) |
|
|
| match = re.search(r"confirm=([0-9A-Za-z_\-]+)\"", text) |
| if match: |
| return match.group(1) |
|
|
| return None |
|
|
|
|
| def download_google_drive_file(dest_path, file_id=GOOGLE_DRIVE_FILE_ID): |
| import requests |
|
|
| dest_path = Path(dest_path) |
| dest_path.parent.mkdir(parents=True, exist_ok=True) |
|
|
| session = requests.Session() |
| response = session.get( |
| GOOGLE_DRIVE_DOWNLOAD_URL, |
| params={"id": file_id}, |
| stream=True, |
| timeout=30, |
| ) |
|
|
| content_type = response.headers.get("Content-Type", "") |
| if "Content-Disposition" not in response.headers and "text/html" in content_type: |
| token = _get_google_drive_confirm_token(response.text) |
| if token: |
| response = session.get( |
| GOOGLE_DRIVE_DOWNLOAD_URL, |
| params={"id": file_id, "confirm": token}, |
| stream=True, |
| timeout=30, |
| ) |
|
|
| if response.status_code != 200: |
| raise RuntimeError( |
| f"Failed to download file from Google Drive (status {response.status_code})" |
| ) |
|
|
| with open(dest_path, "wb") as f: |
| for chunk in response.iter_content(chunk_size=32768): |
| if chunk: |
| f.write(chunk) |
|
|
| return dest_path |
|
|
|
|
| def ensure_store(path): |
| path = Path(path) |
| if path.exists(): |
| return path |
|
|
| if path.name == DEFAULT_PICKLE.name: |
| print( |
| f"Store not found at {path}. Downloading default vector store from Google Drive..." |
| ) |
| download_google_drive_file(path) |
| print(f"Downloaded store to {path}") |
| return path |
|
|
| raise FileNotFoundError(f"Store not found: {path}") |
|
|
|
|
| def load_store(path): |
| import pickle |
|
|
| store_path = ensure_store(path) |
| with open(store_path, "rb") as f: |
| return pickle.load(f) |
|
|
|
|
| def ensure_tensor(embeddings): |
| if torch.is_tensor(embeddings): |
| return embeddings.float().cpu() |
|
|
| return torch.tensor( |
| embeddings, |
| dtype=torch.float32, |
| ) |
|
|
|
|
| def load_model(model_name, device): |
| print(f"Loading model: {model_name}") |
| print(f"Device: {device}") |
|
|
| return SentenceTransformer( |
| model_name, |
| device=device, |
| ) |
|
|
|
|
| def encode_query(model, query): |
| query = ( |
| "Represent this sentence for searching relevant passages: " |
| + query |
| ) |
|
|
| return model.encode( |
| query, |
| convert_to_tensor=True, |
| normalize_embeddings=True, |
| ) |
|
|
|
|
| def retrieve_top_k( |
| query, |
| embeddings, |
| texts, |
| model, |
| top_k=5, |
| threshold=0.35, |
| ): |
| query_embedding = encode_query( |
| model, |
| query, |
| ) |
|
|
| embeddings = ensure_tensor( |
| embeddings |
| ) |
|
|
| scores = util.cos_sim( |
| query_embedding, |
| embeddings, |
| )[0] |
|
|
| top_k = min( |
| top_k, |
| len(texts), |
| ) |
|
|
| top_results = torch.topk( |
| scores, |
| k=top_k, |
| ) |
|
|
| results = [] |
|
|
| for idx, score in zip( |
| top_results.indices, |
| top_results.values, |
| ): |
| score = float(score) |
|
|
| if score >= threshold: |
| results.append( |
| ( |
| score, |
| texts[idx.item()], |
| ) |
| ) |
|
|
| return results |
|
|
|
|
| def print_environment(): |
| print("\n=== Environment ===") |
| print("PyTorch:", torch.__version__) |
| print("CUDA Available:", torch.cuda.is_available()) |
|
|
| if torch.cuda.is_available(): |
| print( |
| "GPU:", |
| torch.cuda.get_device_name(0), |
| ) |
|
|
| print("===================\n") |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser() |
|
|
| parser.add_argument( |
| "--path", |
| default=str(DEFAULT_PICKLE), |
| ) |
|
|
| parser.add_argument( |
| "--query", |
| default="what is diabetes", |
| ) |
|
|
| parser.add_argument( |
| "--top-k", |
| type=int, |
| default=5, |
| ) |
|
|
| parser.add_argument( |
| "--threshold", |
| type=float, |
| default=0.35, |
| ) |
|
|
| parser.add_argument( |
| "--model", |
| default=DEFAULT_MODEL, |
| ) |
|
|
| args = parser.parse_args() |
|
|
| print_environment() |
|
|
| store_path = Path(args.path) |
|
|
| if not store_path.exists(): |
| raise FileNotFoundError( |
| f"Store not found: {store_path}" |
| ) |
|
|
| print("Loading vector store...") |
|
|
| store = load_store(store_path) |
|
|
| embeddings = ensure_tensor( |
| store["embeddings"] |
| ) |
|
|
| texts = store["texts"] |
|
|
| device = get_device() |
|
|
| model = load_model( |
| args.model, |
| device, |
| ) |
|
|
| results = retrieve_top_k( |
| query=args.query, |
| embeddings=embeddings, |
| texts=texts, |
| model=model, |
| top_k=args.top_k, |
| threshold=args.threshold, |
| ) |
|
|
| print("\n=== Results ===\n") |
|
|
| if not results: |
| print( |
| "No documents found above threshold." |
| ) |
| return |
|
|
| context = [] |
|
|
| for i, (score, text) in enumerate( |
| results, |
| start=1, |
| ): |
| print( |
| f"Result {i} | Score: {score:.4f}\n" |
| ) |
|
|
| print(text[:1000]) |
|
|
| print( |
| "\n" |
| + "=" * 80 |
| + "\n" |
| ) |
|
|
| context.append( |
| f"[Document {i}]\n{text}" |
| ) |
|
|
| final_context = "\n\n".join( |
| context |
| ) |
|
|
| print( |
| "\nContext length:", |
| len(final_context), |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |