smart-advisor / src /knowledge_base /vector_store.py
sajaahmed5
Smart Advisor deployment
74e8a7b
Raw
History Blame Contribute Delete
3.5 kB
"""
ChromaDB collection access and similarity/metadata search.
"""
import chromadb
from src.utils.config import DB_PATH, COLLECTION_NAME, SIMILARITY_THRESHOLD
from src.knowledge_base.embeddings import embed_texts_with_retry
_client = chromadb.PersistentClient(path=DB_PATH)
collection = _client.get_collection(COLLECTION_NAME)
def search(
query: str,
top_k: int = 3,
metadata_filter: dict | None = None,
where: dict | None = None,
is_exact_fetch: bool = False,
) -> dict:
"""
Embed query and search ChromaDB.
metadata_filter: optional 'where' clause for similarity search.
where: 'where' clause for a direct metadata lookup (used when is_exact_fetch=True).
is_exact_fetch: skip embedding similarity entirely and fetch ALL chunks matching `where`
via collection.get(). Used for structured category lookups
(study_plan / program_info / scholarship / all_programs comparison)
where the whole matching set is wanted, not a top-k similarity guess.
NOTE: matches current notebook behavior β€” has_answer is returned True even when
documents is empty on the exact-fetch path (only a warning is printed). This means
the fallback-to-similarity-search in multi_query_search never actually triggers on
a truly empty exact-fetch result. Recommend restoring an `if documents:` guard here
if you want the fallback to work as originally intended β€” flagging for your decision.
"""
# ── Study plan: direct metadata lookup, no ranking ─────────────────
if is_exact_fetch and where:
results = collection.get(where=where)
documents = results["documents"]
metadatas = results["metadatas"]
if not documents:
print(" [search] No exact-category chunks matched β€” falling back to similarity search")
scores = [1.0] * len(documents)
return {"has_answer": True, "documents": documents, "metadatas": metadatas,
"scores": scores, "best_score": 1.0}
# ── Embedding-based similarity search ──────────────────────────────
query_embedding = embed_texts_with_retry([query])[0]
query_params = dict(
query_embeddings=[query_embedding],
n_results=top_k,
include=["documents", "metadatas", "distances"],
)
if metadata_filter:
query_params["where"] = metadata_filter
results = collection.query(**query_params)
documents = results["documents"][0]
metadatas = results["metadatas"][0]
distances = results["distances"][0]
# Fallback: if filter matched nothing, retry without the filter
if not documents and metadata_filter:
query_params.pop("where", None)
results = collection.query(**query_params)
documents = results["documents"][0]
metadatas = results["metadatas"][0]
distances = results["distances"][0]
if not documents:
return {"has_answer": False, "documents": [], "metadatas": [], "scores": [], "best_score": 0.0}
scores = [1 - d for d in distances]
best_score = max(scores)
if best_score < SIMILARITY_THRESHOLD:
return {"has_answer": False, "documents": [], "metadatas": [], "scores": [], "best_score": best_score}
return {"has_answer": True, "documents": documents, "metadatas": metadatas, "scores": scores, "best_score": best_score}