Datavision / backend /vector /retriever.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
4.47 kB
"""
Vector retrieval module - REAL implementation using FAISS
Retrieves relevant documents from user's vector store
"""
from vector.store_faiss import FaissStore
from core.llm import embed_text
import numpy as np
def retrieve(query: str, k: int = 5, user_id: str = None):
"""
Retrieve relevant documents from FAISS vector store.
IMPROVED: Ensures results come from ALL uploaded files, not just top-k similar.
Args:
query: Search query string
k: Number of results to return (will return k per file for comprehensive coverage)
user_id: User identifier for per-user store
Returns:
List of document dicts with text, source, and score
"""
if not user_id:
print("⚠️ No user_id provided to retrieve()")
return []
try:
# Load user's FAISS store
store = FaissStore.load_or_create(user_id=user_id, fresh=False)
if store.index.ntotal == 0:
print(f"⚠️ FAISS store for {user_id} is empty (0 vectors)")
return []
print(f"πŸ“š Searching FAISS store with {store.index.ntotal} vectors for user {user_id}")
# Embed the query
query_embedding = embed_text(query)
query_vector = np.array([query_embedding], dtype="float32")
# IMPROVED: Search for MORE results then balance across files
search_k = min(50, max(k * 5, store.index.ntotal)) # Search 50 or 5x requested
distances, indices = store.index.search(query_vector, search_k)
# Group results by source file
results_by_source = {}
all_results = []
for i, (dist, idx) in enumerate(zip(distances[0], indices[0])):
if idx < 0 or idx >= len(store.meta):
continue
meta = store.meta[idx]
source = meta.get("source", "Unknown")
# Calculate similarity score (convert L2 distance to similarity)
score = 1 / (1 + dist)
result = {
"text": meta.get("text", ""),
"source": source,
"score": float(score),
"chunk": meta.get("chunk", i),
"metadata": meta
}
# Group by source
if source not in results_by_source:
results_by_source[source] = []
results_by_source[source].append(result)
all_results.append(result)
# CRITICAL: Ensure we get results from EACH file
num_files = len(results_by_source)
print(f"πŸ“ Found results from {num_files} files: {list(results_by_source.keys())}")
if num_files > 1:
# Balance results across all files
chunks_per_file = max(2, k // num_files + 1)
balanced_results = []
for source, file_results in results_by_source.items():
# Take top chunks from each file (sorted by score)
file_results.sort(key=lambda x: x['score'], reverse=True)
balanced_results.extend(file_results[:chunks_per_file])
print(f" πŸ“„ {source}: {len(file_results[:chunks_per_file])} chunks")
# Sort balanced results by score for final ordering
balanced_results.sort(key=lambda x: x['score'], reverse=True)
results = balanced_results[:k * 2] # Return more for comprehensive coverage
else:
# Single file - just return top k
results = all_results[:k]
print(f"βœ… Retrieved {len(results)} documents from FAISS (balanced across {num_files} files)")
return results
except Exception as e:
print(f"⚠️ Error in retrieve(): {e}")
import traceback
traceback.print_exc()
return []
def hybrid_retrieve(query: str, k: int = 5, user_id: str = None):
"""
Hybrid retrieval combining FAISS vector search with keyword matching.
Falls back to standard retrieve if hybrid index not available.
Args:
query: Search query string
k: Number of results to return
user_id: User identifier
Returns:
List of document dicts
"""
# For now, use standard FAISS retrieval
# TODO: Implement BM25 keyword matching + score fusion
return retrieve(query, k=k, user_id=user_id)