Hybrid_Rag_System / query_understanding.py
MaxyZand's picture
Upload 15 files
f565efa verified
Raw
History Blame Contribute Delete
8.77 kB
import re
import spacy
from spacy.pipeline import EntityRuler
from transformers import pipeline
from sentence_transformers import SentenceTransformer, util
# -----------------------------
# Load Models
# -----------------------------
print("Loading models...")
# Intent classifier
intent_classifier = pipeline(
"zero-shot-classification",
model="facebook/bart-large-mnli"
)
# Query rewriting model
# NOTE: flan-t5 is a seq2seq model, so it needs "text2text-generation",
# not "text-generation" (that pipeline expects a decoder-only/causal LM
# and will error or behave incorrectly with flan-t5).
rewriter = pipeline(
"text2text-generation",
model="google/flan-t5-base"
)
# Embedding model
embedding_model = SentenceTransformer(
"all-MiniLM-L6-v2"
)
# NER model
nlp = spacy.load(
"en_core_web_sm"
)
# Entity Ruler patterns — registered once, outside the function.
# Adding a pipe named "entity_ruler" on every call to extract_entities()
# would raise "already exists in pipeline" from the second call onward.
ENTITY_PATTERNS = [
# =======================
# AI Concepts
# =======================
{"label": "AI_CONCEPT", "pattern": "RAG"},
{"label": "AI_CONCEPT", "pattern": "Retrieval Augmented Generation"},
{"label": "AI_CONCEPT", "pattern": "LLM"},
{"label": "AI_CONCEPT", "pattern": "Generative AI"},
{"label": "AI_CONCEPT", "pattern": "Machine Learning"},
{"label": "AI_CONCEPT", "pattern": "Deep Learning"},
# =======================
# AI Models
# =======================
{"label": "AI_MODEL", "pattern": "GPT-4"},
{"label": "AI_MODEL", "pattern": "GPT-4o"},
{"label": "AI_MODEL", "pattern": "GPT-5"},
{"label": "AI_MODEL", "pattern": "Llama 2"},
{"label": "AI_MODEL", "pattern": "Llama 3"},
{"label": "AI_MODEL", "pattern": "Claude"},
{"label": "AI_MODEL", "pattern": "Gemini"},
{"label": "AI_MODEL", "pattern": "Mistral"},
{"label": "AI_MODEL", "pattern": "DeepSeek"},
# =======================
# Frameworks
# =======================
{"label": "FRAMEWORK", "pattern": "LangChain"},
{"label": "FRAMEWORK", "pattern": "LlamaIndex"},
{"label": "FRAMEWORK", "pattern": "Haystack"},
{"label": "FRAMEWORK", "pattern": "CrewAI"},
{"label": "FRAMEWORK", "pattern": "LangGraph"},
# =======================
# Vector Databases
# =======================
{"label": "VECTOR_DATABASE", "pattern": "FAISS"},
{"label": "VECTOR_DATABASE", "pattern": "Chroma"},
{"label": "VECTOR_DATABASE", "pattern": "Pinecone"},
{"label": "VECTOR_DATABASE", "pattern": "Weaviate"},
{"label": "VECTOR_DATABASE", "pattern": "Milvus"},
{"label": "VECTOR_DATABASE", "pattern": "Qdrant"},
# =======================
# Databases
# =======================
{"label": "DATABASE", "pattern": "MongoDB"},
{"label": "DATABASE", "pattern": "MySQL"},
{"label": "DATABASE", "pattern": "PostgreSQL"},
{"label": "DATABASE", "pattern": "SQLite"},
# =======================
# Programming Languages
# =======================
{"label": "LANGUAGE", "pattern": "Python"},
{"label": "LANGUAGE", "pattern": "Java"},
{"label": "LANGUAGE", "pattern": "C++"},
{"label": "LANGUAGE", "pattern": "JavaScript"},
# =======================
# Cloud Platforms
# =======================
{"label": "CLOUD", "pattern": "AWS"},
{"label": "CLOUD", "pattern": "Azure"},
{"label": "CLOUD", "pattern": "Google Cloud"},
]
_ruler = nlp.add_pipe("entity_ruler", before="ner")
_ruler.add_patterns(ENTITY_PATTERNS)
# -----------------------------
# 1. Query Preprocessing
# -----------------------------
def preprocess(query):
query = query.lower()
query = re.sub(
r"[^a-zA-Z0-9 ]",
"",
query
)
return query.strip()
# -----------------------------
# 2. Intent Classification
# -----------------------------
#
# Only 3 downstream agents exist (Document, SQL, Web Search), so the
# zero-shot labels map directly onto them instead of a larger intent
# taxonomy that then needs re-bucketing at routing time.
INTENT_LABELS = [
"document lookup", # -> Document Agent (RAG over stored docs)
"database query", # -> SQL Agent (structured/transactional data)
"web search" # -> Web Search Agent (fresh/external info)
]
def classify_intent(query):
result = intent_classifier(
query,
INTENT_LABELS
)
return {
"intent": result["labels"][0],
"confidence": round(
result["scores"][0],
3
)
}
# -----------------------------
# 3. Entity Extraction
# -----------------------------
def extract_entities(query):
doc = nlp(query)
entities = []
for ent in doc.ents:
entities.append(
{
"text": ent.text,
"type": ent.label_
}
)
return entities
# -----------------------------
# 4. Complexity Detection
# -----------------------------
def detect_complexity(query):
words=query.split()
multi_words=[
"compare",
"difference",
"and",
"vs",
"recommend"
]
if len(words)>15:
return "complex"
for word in multi_words:
if word in query.lower():
return "multi-hop"
return "simple"
# -----------------------------
# 5. Query Rewriting
# -----------------------------
def rewrite_query(query):
prompt=f"""
Rewrite this query for better search retrieval.
Keep the meaning same.
Query:
{query}
Better query:
"""
result=rewriter(
prompt,
max_new_tokens=50
)
return result[0]["generated_text"]
# -----------------------------
# 6. Query Expansion
# -----------------------------
def expand_query(query):
related_words={
"rag":[
"retrieval augmented generation",
"vector database",
"document retrieval"
],
"llm":[
"large language model",
"generative AI"
],
"ai":[
"machine learning",
"deep learning"
]
}
expansion=[]
for key,value in related_words.items():
if key in query.lower():
expansion.extend(value)
return expansion
# -----------------------------
# 7. Query Decomposition
# -----------------------------
def decompose_query(query):
keywords=[
"compare",
"and",
"vs",
"difference"
]
for key in keywords:
if key in query.lower():
parts=query.split(key)
return [
part.strip()
for part in parts
if part.strip()
]
return [query]
# -----------------------------
# 8. Query Routing
# -----------------------------
#
# Exactly 3 agents. classify_intent() already returns one of
# INTENT_LABELS, so this is a direct 1:1 map with a safe default
# rather than a many-to-one bucket of older intent names.
ROUTES = {
"document lookup": "Document Agent",
"database query": "SQL Agent",
"web search": "Web Search Agent",
}
def route_query(intent):
return ROUTES.get(
intent,
"Document Agent" # default fallback if confidence is low / unclear
)
# -----------------------------
# MAIN PIPELINE
# -----------------------------
def query_understanding(query):
result={}
# preprocessing
clean_query=preprocess(query)
result["original_query"]=query
result["clean_query"]=clean_query
# intent
intent=classify_intent(
clean_query
)
result["intent"]=intent
# entities
result["entities"]=extract_entities(
query
)
# complexity
result["complexity"]=detect_complexity(
query
)
# rewriting
result["rewritten_query"]=rewrite_query(
query
)
# expansion
result["expanded_terms"]=expand_query(
query
)
# decomposition
result["sub_queries"]=decompose_query(
query
)
# routing
result["route"]=route_query(
intent["intent"]
)
return result
# -----------------------------
# TEST
# -----------------------------
if __name__ == "__main__":
query = "What is Matma Gandi?"
output = query_understanding(query)
print("\n=========================================")
print("QUERY:", query.strip())
print("=========================================")
for key, value in output.items():
print("\n", key)
print("----------------")
print(value)