Martechsol commited on
Commit Β·
d58c972
1
Parent(s): 6ece568
Eliminate first Groq API call: replace LLM query rewrite with instant local keyword expander (~3-6s saved per request)
Browse files- app/services/rag_pipeline.py +84 -7
app/services/rag_pipeline.py
CHANGED
|
@@ -20,6 +20,82 @@ def _cache_key(message: str) -> str:
|
|
| 20 |
"""Returns a stable hash key for a normalized message string."""
|
| 21 |
return hashlib.md5(message.lower().strip().encode()).hexdigest()
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
# RRF scores are small (e.g. 0.016β0.033), so threshold must be very low
|
| 24 |
RELEVANCE_THRESHOLD = 0.01
|
| 25 |
# Cross-encoder logit > 0 means > 50% relevance probability
|
|
@@ -51,9 +127,9 @@ class RAGPipeline:
|
|
| 51 |
logger.info("Cache HIT for: '%s'", message[:40])
|
| 52 |
return _answer_cache[key]
|
| 53 |
|
| 54 |
-
# ββ Step 1:
|
| 55 |
-
queries =
|
| 56 |
-
logger.info("
|
| 57 |
|
| 58 |
# ββ Step 2: Collect unique chunks across all queries ββ
|
| 59 |
seen_ids = set()
|
|
@@ -81,11 +157,12 @@ class RAGPipeline:
|
|
| 81 |
return {"reply": reply, "retrieved_chunks": []}
|
| 82 |
|
| 83 |
# ββ Step 4: Deep reranking via Cross-Encoder ββ
|
| 84 |
-
#
|
|
|
|
| 85 |
rerank_query = message
|
| 86 |
-
if len(queries) >
|
| 87 |
-
rerank_query = f"{message} {queries[
|
| 88 |
-
|
| 89 |
reranked_chunks = self.reranker.rerank(rerank_query, initial_chunks, top_n=self.max_context_chunks)
|
| 90 |
|
| 91 |
# Filter by rerank score threshold
|
|
|
|
| 20 |
"""Returns a stable hash key for a normalized message string."""
|
| 21 |
return hashlib.md5(message.lower().strip().encode()).hexdigest()
|
| 22 |
|
| 23 |
+
# ββ Local intent-based query expander ββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
# Replaces the async Groq API call (llama-3.1-8b-instant) with deterministic
|
| 25 |
+
# Python rules β runs in microseconds, saves 3β6s per request.
|
| 26 |
+
# Rules mirror the exact intent-detection logic from the old LLM prompt.
|
| 27 |
+
# IMPORTANT: ordered most-specific β least-specific to prevent false matches.
|
| 28 |
+
_INTENT_MAP: List[tuple] = [
|
| 29 |
+
# ββ Leave types (most specific first) ββ
|
| 30 |
+
("paid leave", ["casual leave sick leave annual leave maternity paternity hajj bereavement study unauthorized",
|
| 31 |
+
"paid leave types employee entitlement days count"]),
|
| 32 |
+
("all leave", ["casual leave sick leave annual leave maternity paternity hajj bereavement study unauthorized",
|
| 33 |
+
"all leave types employee entitlement days count"]),
|
| 34 |
+
("maternity", ["maternity leave days duration policy", "paid leave maternity employee"]),
|
| 35 |
+
("paternity", ["paternity leave days duration policy", "paid leave paternity employee"]),
|
| 36 |
+
("hajj", ["hajj leave days duration policy", "paid leave hajj religious"]),
|
| 37 |
+
("bereavement", ["bereavement leave death family days", "compassionate leave policy"]),
|
| 38 |
+
("sick leave", ["sick leave days count policy", "medical leave employee entitlement"]),
|
| 39 |
+
("casual leave", ["casual leave days count policy", "leave types employee"]),
|
| 40 |
+
("annual leave", ["annual leave days count policy", "leave entitlement per year"]),
|
| 41 |
+
("study leave", ["study leave education policy days", "employee study leave entitlement"]),
|
| 42 |
+
("leave", ["casual leave sick leave annual leave maternity paternity hajj bereavement study unauthorized",
|
| 43 |
+
"leave types employee entitlement days"]),
|
| 44 |
+
# ββ Office timing ββ
|
| 45 |
+
("office hour", ["office working hours schedule", "workday start end time shift"]),
|
| 46 |
+
("work hour", ["office working hours schedule", "workday start end time shift"]),
|
| 47 |
+
("timing", ["office working hours schedule", "workday start end time shift hours"]),
|
| 48 |
+
("schedule", ["office working hours schedule", "workday start end time"]),
|
| 49 |
+
# ββ Salary / Pay ββ
|
| 50 |
+
("salary", ["salary structure payroll compensation amount", "monthly pay increment deduction"]),
|
| 51 |
+
("pay", ["salary structure payroll compensation", "payment date schedule"]),
|
| 52 |
+
("payroll", ["salary payroll structure compensation", "monthly pay deduction"]),
|
| 53 |
+
("compensation", ["salary compensation structure payroll", "monthly pay amount"]),
|
| 54 |
+
# ββ Benefits / Allowances ββ
|
| 55 |
+
("allowance", ["allowances perks medical bonuses fuel transport reimbursements", "employee benefits"]),
|
| 56 |
+
("benefit", ["allowances perks medical bonuses reimbursements", "employee benefits privileges"]),
|
| 57 |
+
("perk", ["employee perks benefits extras privileges", "allowances bonuses"]),
|
| 58 |
+
("fuel", ["fuel allowance transport reimbursement conveyance petrol"]),
|
| 59 |
+
("medical", ["medical allowance health insurance coverage", "medical benefits employee"]),
|
| 60 |
+
("transport", ["transport allowance fuel reimbursement conveyance"]),
|
| 61 |
+
("bonus", ["bonus performance incentive annual eid festival reward"]),
|
| 62 |
+
# ββ Termination / Resignation ββ
|
| 63 |
+
("terminat", ["termination resignation procedure process steps", "notice period exit policy"]),
|
| 64 |
+
("resign", ["resignation procedure steps notice period", "termination exit process"]),
|
| 65 |
+
("notice period", ["notice period resignation termination duration days"]),
|
| 66 |
+
# ββ Other HR topics ββ
|
| 67 |
+
("probat", ["probation period duration conditions employee"]),
|
| 68 |
+
("overtime", ["overtime compensation extra hours payment policy"]),
|
| 69 |
+
("increment", ["salary increment raise annual review appraisal performance"]),
|
| 70 |
+
("appraisal", ["performance appraisal review increment salary raise"]),
|
| 71 |
+
("attendance", ["attendance policy punctuality late arrival absenteeism"]),
|
| 72 |
+
("dress code", ["dress code uniform attire professional clothing policy"]),
|
| 73 |
+
("remote", ["remote work work from home WFH policy telecommute"]),
|
| 74 |
+
("grievance", ["grievance complaint procedure policy employee rights"]),
|
| 75 |
+
("discipline", ["disciplinary action policy procedure employee"]),
|
| 76 |
+
("code of conduct", ["code of conduct policy employee behaviour rules"]),
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _expand_query_locally(message: str) -> List[str]:
|
| 81 |
+
"""
|
| 82 |
+
Expands a user query into targeted search strings using keyword rules.
|
| 83 |
+
Replaces the async LLM rewrite call β runs in microseconds, zero API cost.
|
| 84 |
+
Mirrors the exact intent-detection logic previously in the llama-3.1-8b prompt.
|
| 85 |
+
"""
|
| 86 |
+
msg_lower = message.lower()
|
| 87 |
+
queries: List[str] = [message] # Original query always first
|
| 88 |
+
|
| 89 |
+
for keyword, variants in _INTENT_MAP:
|
| 90 |
+
if keyword in msg_lower:
|
| 91 |
+
for v in variants:
|
| 92 |
+
if v not in queries:
|
| 93 |
+
queries.append(v)
|
| 94 |
+
break # Only apply first (most specific) matching intent
|
| 95 |
+
|
| 96 |
+
return queries[:3] # Cap at 3, consistent with previous LLM behaviour
|
| 97 |
+
|
| 98 |
+
|
| 99 |
# RRF scores are small (e.g. 0.016β0.033), so threshold must be very low
|
| 100 |
RELEVANCE_THRESHOLD = 0.01
|
| 101 |
# Cross-encoder logit > 0 means > 50% relevance probability
|
|
|
|
| 127 |
logger.info("Cache HIT for: '%s'", message[:40])
|
| 128 |
return _answer_cache[key]
|
| 129 |
|
| 130 |
+
# ββ Step 1: Expand query locally (no API call β instant) ββ
|
| 131 |
+
queries = _expand_query_locally(message)
|
| 132 |
+
logger.info("Expanded %d queries for: '%s' β %s", len(queries), message[:40], queries)
|
| 133 |
|
| 134 |
# ββ Step 2: Collect unique chunks across all queries ββ
|
| 135 |
seen_ids = set()
|
|
|
|
| 157 |
return {"reply": reply, "retrieved_chunks": []}
|
| 158 |
|
| 159 |
# ββ Step 4: Deep reranking via Cross-Encoder ββ
|
| 160 |
+
# Enrich the reranker query with the first expanded variant (queries[1])
|
| 161 |
+
# to give the cross-encoder broader semantic context.
|
| 162 |
rerank_query = message
|
| 163 |
+
if len(queries) > 1:
|
| 164 |
+
rerank_query = f"{message} {queries[1]}"
|
| 165 |
+
|
| 166 |
reranked_chunks = self.reranker.rerank(rerank_query, initial_chunks, top_n=self.max_context_chunks)
|
| 167 |
|
| 168 |
# Filter by rerank score threshold
|