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
Files changed (1) hide show
  1. 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: Generate multiple search queries for broad coverage ──
55
- queries = await self.llm_service.generate_multi_queries(message, history)
56
- logger.info("Generated %d queries for: '%s' β†’ %s", len(queries), message[:40], queries)
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
- # Enrihc the reranker query with the LLM's expanded search terms
 
85
  rerank_query = message
86
- if len(queries) > 0 and queries[0] != message:
87
- rerank_query = f"{message} {queries[0]}"
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