|
|
import os |
|
|
os.environ['TOKENIZERS_PARALLELISM'] = 'false' |
|
|
from flask import Flask, request, jsonify |
|
|
from flask_cors import CORS |
|
|
import numpy as np |
|
|
import json |
|
|
import traceback |
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
CORS(app, origins=["http://127.0.0.1:3000", "http://localhost:3000", "https://rag-huggingface.vercel.app"], supports_credentials=True) |
|
|
|
|
|
|
|
|
INDEX_FILE = "index.faiss" |
|
|
MAP_FILE = "index_to_metadata.pkl" |
|
|
EMBEDDING_MODEL = 'all-MiniLM-L6-v2' |
|
|
|
|
|
MODEL_DATA_DIR = os.path.join(os.path.dirname(__file__), 'model_data_json') |
|
|
|
|
|
|
|
|
|
|
|
faiss = None |
|
|
pickle = None |
|
|
index = None |
|
|
index_to_metadata = None |
|
|
model = None |
|
|
SentenceTransformer = None |
|
|
RESOURCES_LOADED = False |
|
|
|
|
|
|
|
|
def load_resources(): |
|
|
"""Loads all necessary resources (model, index, map) only once.""" |
|
|
global faiss, pickle, index, index_to_metadata, model, SentenceTransformer, RESOURCES_LOADED |
|
|
if RESOURCES_LOADED: |
|
|
print("Resources already loaded.") |
|
|
return |
|
|
|
|
|
print("Loading resources...") |
|
|
try: |
|
|
|
|
|
print("Importing Faiss and Pickle...") |
|
|
import faiss as faiss_local |
|
|
import pickle as pickle_local |
|
|
faiss = faiss_local |
|
|
pickle = pickle_local |
|
|
print("Faiss and Pickle imported successfully.") |
|
|
|
|
|
|
|
|
print(f"Importing SentenceTransformer and loading model: {EMBEDDING_MODEL}") |
|
|
from sentence_transformers import SentenceTransformer as SentenceTransformer_local |
|
|
SentenceTransformer = SentenceTransformer_local |
|
|
model_local = SentenceTransformer(EMBEDDING_MODEL) |
|
|
model = model_local |
|
|
print("Sentence transformer model loaded successfully.") |
|
|
|
|
|
|
|
|
index_path = os.path.join(os.path.dirname(__file__), INDEX_FILE) |
|
|
print(f"Loading FAISS index from: {index_path}") |
|
|
if not os.path.exists(index_path): |
|
|
raise FileNotFoundError(f"FAISS index file not found at {index_path}") |
|
|
index_local = faiss.read_index(index_path) |
|
|
index = index_local |
|
|
print("FAISS index loaded successfully.") |
|
|
|
|
|
|
|
|
map_path = os.path.join(os.path.dirname(__file__), MAP_FILE) |
|
|
print(f"Loading index-to-Metadata map from: {map_path}") |
|
|
if not os.path.exists(map_path): |
|
|
raise FileNotFoundError(f"Metadata map file not found at {map_path}") |
|
|
with open(map_path, 'rb') as f: |
|
|
index_to_metadata_local = pickle.load(f) |
|
|
index_to_metadata = index_to_metadata_local |
|
|
print("Index-to-Metadata map loaded successfully.") |
|
|
|
|
|
print("All resources loaded successfully.") |
|
|
RESOURCES_LOADED = True |
|
|
|
|
|
except FileNotFoundError as fnf_error: |
|
|
print(f"Error: {fnf_error}") |
|
|
print(f"Please ensure {INDEX_FILE} and {MAP_FILE} exist in the 'backend' directory relative to app.py.") |
|
|
print("You might need to run 'python build_index.py' first.") |
|
|
RESOURCES_LOADED = False |
|
|
except ImportError as import_error: |
|
|
print(f"Import Error loading resources: {import_error}") |
|
|
traceback.print_exc() |
|
|
RESOURCES_LOADED = False |
|
|
except Exception as e: |
|
|
print(f"Unexpected error loading resources: {e}") |
|
|
traceback.print_exc() |
|
|
RESOURCES_LOADED = False |
|
|
|
|
|
|
|
|
|
|
|
load_resources() |
|
|
|
|
|
|
|
|
@app.route('/search', methods=['POST']) |
|
|
def search(): |
|
|
"""Handles search requests, embedding the query and searching the FAISS index.""" |
|
|
|
|
|
if not RESOURCES_LOADED: |
|
|
|
|
|
|
|
|
print("Error: Search request received, but resources are not loaded.") |
|
|
return jsonify({"error": "Backend resources not initialized. Check server logs."}), 500 |
|
|
|
|
|
|
|
|
if model is None or index is None or index_to_metadata is None or faiss is None: |
|
|
print("Error: Search request received, but some core components (model, index, map, faiss) are None.") |
|
|
return jsonify({"error": "Backend components inconsistency. Check server logs."}), 500 |
|
|
|
|
|
data = request.get_json() |
|
|
if not data or 'query' not in data: |
|
|
return jsonify({"error": "Missing 'query' in request body"}), 400 |
|
|
|
|
|
query = data['query'] |
|
|
top_k = data.get('top_k', 10) |
|
|
|
|
|
try: |
|
|
|
|
|
|
|
|
if model is None: |
|
|
return jsonify({"error": "Model not loaded."}), 500 |
|
|
query_embedding = model.encode([query], convert_to_numpy=True).astype('float32') |
|
|
|
|
|
|
|
|
|
|
|
if index is None: |
|
|
return jsonify({"error": "Index not loaded."}), 500 |
|
|
distances, indices = index.search(query_embedding, top_k) |
|
|
|
|
|
|
|
|
results = [] |
|
|
if indices.size > 0: |
|
|
|
|
|
if index_to_metadata is None: |
|
|
print("Error: index_to_metadata is None during result processing.") |
|
|
return jsonify({"error": "Metadata map not loaded."}), 500 |
|
|
|
|
|
for i in range(len(indices[0])): |
|
|
idx = indices[0][i] |
|
|
dist = distances[0][i] |
|
|
|
|
|
|
|
|
if idx < 0 or idx not in index_to_metadata: |
|
|
print(f"Warning: Index {idx} out of bounds or not found in metadata mapping.") |
|
|
continue |
|
|
|
|
|
metadata = index_to_metadata[idx].copy() |
|
|
metadata['distance'] = float(dist) |
|
|
|
|
|
|
|
|
model_id = metadata.get('model_id') |
|
|
description = None |
|
|
|
|
|
if model_id and MODEL_DATA_DIR: |
|
|
filename = model_id.replace('/', '_') + '.json' |
|
|
filepath = os.path.join(MODEL_DATA_DIR, filename) |
|
|
if os.path.exists(filepath): |
|
|
try: |
|
|
with open(filepath, 'r', encoding='utf-8') as f: |
|
|
model_data = json.load(f) |
|
|
description = model_data.get('description') |
|
|
except Exception as e: |
|
|
print(f"Error reading description file {filepath}: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
metadata['description'] = description or 'No description available.' |
|
|
|
|
|
|
|
|
results.append(metadata) |
|
|
|
|
|
else: |
|
|
print("Warning: FAISS search returned empty indices.") |
|
|
|
|
|
return jsonify({"results": results}) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error during search: {e}") |
|
|
traceback.print_exc() |
|
|
return jsonify({"error": "An error occurred during search."}), 500 |
|
|
|
|
|
|