quantumbit commited on
Commit
9825e99
·
verified ·
1 Parent(s): 88ea47a

Upload folder using huggingface_hub

Browse files
rag_system/cache.py CHANGED
@@ -106,7 +106,11 @@ def _tag_hash(value: str) -> str:
106
 
107
 
108
  def _vector_bytes(vec: list[float]) -> bytes:
109
- return np.array(vec, dtype=np.float32).tobytes()
 
 
 
 
110
 
111
 
112
  def _decode(value: bytes | str | None) -> Optional[str]:
 
106
 
107
 
108
  def _vector_bytes(vec: list[float]) -> bytes:
109
+ arr = np.array(vec, dtype=np.float32)
110
+ norm = np.linalg.norm(arr)
111
+ if norm > 0:
112
+ arr = arr / norm
113
+ return arr.tobytes()
114
 
115
 
116
  def _decode(value: bytes | str | None) -> Optional[str]:
rag_system/config.py CHANGED
@@ -48,7 +48,7 @@ class Settings(BaseSettings):
48
  cache_enabled: bool = False
49
  redis_url: str = "redis://localhost:6379"
50
  cache_ttl_seconds: int = 3600
51
- semantic_cache_threshold: float = 0.95
52
 
53
  #api
54
  api_title: str = "Production RAG API"
 
48
  cache_enabled: bool = False
49
  redis_url: str = "redis://localhost:6379"
50
  cache_ttl_seconds: int = 3600
51
+ semantic_cache_threshold: float = 0.9
52
 
53
  #api
54
  api_title: str = "Production RAG API"
rag_system/guardrails.py CHANGED
@@ -267,11 +267,26 @@ def _check_query_llama_guard(query: str) -> GuardrailResult:
267
  try:
268
  conversation = [{"role": "user", "content": query}]
269
 
270
- input_ids = _llama_guard_tokenizer.apply_chat_template(
271
- conversation,
272
- return_tensors="pt",
273
- ).to(_llama_guard_model.device)
274
- attention_mask = input_ids.new_ones(input_ids.shape)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
  prompt_len = input_ids.shape[1]
277
  output = _llama_guard_model.generate(
@@ -326,7 +341,11 @@ def _check_query_llama_guard(query: str) -> GuardrailResult:
326
  logger.info("Llama Guard passed query | verdict='%s'", verdict)
327
  return GuardrailResult(allowed=True, sanitized_text=query)
328
  except Exception as exc:
329
- logger.warning("Llama Guard runtime check failed; falling back to regex checks: %r", exc)
 
 
 
 
330
  return GuardrailResult(allowed=True, sanitized_text=query)
331
 
332
  def check_query(query: str) -> GuardrailResult:
 
267
  try:
268
  conversation = [{"role": "user", "content": query}]
269
 
270
+ if hasattr(_llama_guard_tokenizer, "apply_chat_template"):
271
+ tokenized = _llama_guard_tokenizer.apply_chat_template(
272
+ conversation,
273
+ return_tensors="pt",
274
+ )
275
+ else:
276
+ prompt = f"<|user|>\n{query}\n<|assistant|>"
277
+ tokenized = _llama_guard_tokenizer(prompt, return_tensors="pt")
278
+
279
+ if isinstance(tokenized, dict):
280
+ input_ids = tokenized["input_ids"].to(_llama_guard_model.device)
281
+ attention_mask = tokenized.get("attention_mask")
282
+ if attention_mask is not None:
283
+ attention_mask = attention_mask.to(_llama_guard_model.device)
284
+ else:
285
+ input_ids = tokenized.to(_llama_guard_model.device)
286
+ attention_mask = None
287
+
288
+ if attention_mask is None:
289
+ attention_mask = input_ids.new_ones(input_ids.shape)
290
 
291
  prompt_len = input_ids.shape[1]
292
  output = _llama_guard_model.generate(
 
341
  logger.info("Llama Guard passed query | verdict='%s'", verdict)
342
  return GuardrailResult(allowed=True, sanitized_text=query)
343
  except Exception as exc:
344
+ logger.warning(
345
+ "Llama Guard runtime check failed; falling back to regex checks: %r",
346
+ exc,
347
+ exc_info=True,
348
+ )
349
  return GuardrailResult(allowed=True, sanitized_text=query)
350
 
351
  def check_query(query: str) -> GuardrailResult:
rag_system/query_engine.py CHANGED
@@ -8,6 +8,7 @@ Core RAG query pipeline:
8
  6. Return answer + sources
9
  """
10
  import hashlib
 
11
  import logging
12
  import re
13
  import time
@@ -52,6 +53,16 @@ _llm = _build_llm()
52
  _SECTION_REF_RE = re.compile(r"\b\d+\.\d+\b")
53
  _SECTION_HINT_RE = re.compile(r"\b(section|clause|exclusion|code|excl)\b", re.IGNORECASE)
54
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  def _should_preserve_exact_reference(query: str) -> bool:
57
  """
@@ -92,6 +103,39 @@ def _cache_params_key_v2(
92
  f"{_fmt_param(mmr_lambda)}:{_fmt_param(bm25_weight)}:{_fmt_param(vector_weight)}"
93
  )
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  # Query rewriting
96
  async def rewrite_query(query: str) -> str:
97
  """
@@ -264,6 +308,19 @@ async def query(
264
  retrieval_query = await hyde_query_expansion(standalone)
265
  else:
266
  retrieval_query = await rewrite_query(standalone)
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  # 6. Retrieve — multi-doc aware
269
  if len(collections) > 1:
@@ -474,6 +531,23 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
474
  "rewritten": retrieval_query,
475
  })
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  # --- Document routing (multi-doc) ---
478
  if len(collections) > 1:
479
  scoped = detect_query_scope(retrieval_query, collections)
@@ -651,6 +725,13 @@ async def stream_query(request: QueryRequest) -> AsyncIterator[str]:
651
  logger.info("Skipping query rewrite to preserve section/clause reference: '%s'", standalone)
652
  else:
653
  retrieval_query = await rewrite_query(standalone)
 
 
 
 
 
 
 
654
  docs_with_scores = await retrieve(
655
  retrieval_query, request.collection_name, request.retrieval_mode.value
656
  )
 
8
  6. Return answer + sources
9
  """
10
  import hashlib
11
+ import json
12
  import logging
13
  import re
14
  import time
 
53
  _SECTION_REF_RE = re.compile(r"\b\d+\.\d+\b")
54
  _SECTION_HINT_RE = re.compile(r"\b(section|clause|exclusion|code|excl)\b", re.IGNORECASE)
55
 
56
+ _RAG_DECISION_PROMPT = (
57
+ "You are a routing assistant for a retrieval-augmented chat system.\n"
58
+ "Decide if the user's question can be answered using ONLY the prior chat history.\n"
59
+ "If the history provides enough info to answer confidently, respond with JSON:\n"
60
+ '{"use_rag": false, "answer": "..."}\n'
61
+ "If not, respond with JSON:\n"
62
+ '{"use_rag": true, "answer": ""}\n'
63
+ "Rules: Use only chat history, do not guess. If unsure, set use_rag true. Output JSON only."
64
+ )
65
+
66
 
67
  def _should_preserve_exact_reference(query: str) -> bool:
68
  """
 
103
  f"{_fmt_param(mmr_lambda)}:{_fmt_param(bm25_weight)}:{_fmt_param(vector_weight)}"
104
  )
105
 
106
+
107
+ async def _decide_rag_or_answer(
108
+ question: str,
109
+ history: list[dict],
110
+ llm: ChatOpenAI,
111
+ ) -> tuple[bool, Optional[str]]:
112
+ if not history:
113
+ return True, None
114
+
115
+ messages = build_lc_messages(history, _RAG_DECISION_PROMPT)
116
+ messages.append(HumanMessage(content=f"User question: {question}"))
117
+
118
+ try:
119
+ response = await llm.ainvoke(messages)
120
+ raw = response.content.strip()
121
+ data = None
122
+ try:
123
+ data = json.loads(raw)
124
+ except Exception:
125
+ match = re.search(r"\{.*\}", raw, re.DOTALL)
126
+ if match:
127
+ data = json.loads(match.group(0))
128
+ if not isinstance(data, dict):
129
+ return True, None
130
+ use_rag = bool(data.get("use_rag", True))
131
+ answer = data.get("answer") if not use_rag else None
132
+ if not use_rag and isinstance(answer, str) and answer.strip():
133
+ return False, answer.strip()
134
+ return True, None
135
+ except Exception:
136
+ logger.warning("RAG routing decision failed; defaulting to retrieval", exc_info=True)
137
+ return True, None
138
+
139
  # Query rewriting
140
  async def rewrite_query(query: str) -> str:
141
  """
 
308
  retrieval_query = await hyde_query_expansion(standalone)
309
  else:
310
  retrieval_query = await rewrite_query(standalone)
311
+
312
+ # 5.5 Decide if retrieval is needed based on chat history
313
+ use_rag, history_answer = await _decide_rag_or_answer(standalone, trimmed_history, _llm)
314
+ if not use_rag and history_answer:
315
+ latency_ms = round((time.monotonic() - start) * 1000, 2)
316
+ return QueryResponse(
317
+ answer=history_answer,
318
+ sources=[],
319
+ session_id=request.session_id,
320
+ rewritten_query=retrieval_query if retrieval_query != request.query else None,
321
+ cached=False,
322
+ latency_ms=latency_ms,
323
+ )
324
 
325
  # 6. Retrieve — multi-doc aware
326
  if len(collections) > 1:
 
531
  "rewritten": retrieval_query,
532
  })
533
 
534
+ use_rag, history_answer = await _decide_rag_or_answer(standalone, trimmed_history, _llm)
535
+ if not use_rag and history_answer:
536
+ latency_ms = round((time.monotonic() - start) * 1000, 2)
537
+ yield emit("rag_decision", "done", {"use_rag": False, "source": "history"})
538
+ yield emit("generation_start", "done", {"model": settings.chat_model, "source": "history"})
539
+ yield emit("complete", "done", {
540
+ "answer": history_answer,
541
+ "sources": [],
542
+ "rewritten_query": retrieval_query if retrieval_query != request.query else None,
543
+ "latency_ms": latency_ms,
544
+ "session_id": request.session_id,
545
+ "cached": False,
546
+ })
547
+ yield "data: [DONE]\n\n"
548
+ return
549
+ yield emit("rag_decision", "done", {"use_rag": True})
550
+
551
  # --- Document routing (multi-doc) ---
552
  if len(collections) > 1:
553
  scoped = detect_query_scope(retrieval_query, collections)
 
725
  logger.info("Skipping query rewrite to preserve section/clause reference: '%s'", standalone)
726
  else:
727
  retrieval_query = await rewrite_query(standalone)
728
+
729
+ history = [h.model_dump() for h in request.history]
730
+ trimmed_history = trim_history_to_budget(history)
731
+ use_rag, history_answer = await _decide_rag_or_answer(standalone, trimmed_history, _llm)
732
+ if not use_rag and history_answer:
733
+ yield f"data: {history_answer}\n\n"
734
+ return
735
  docs_with_scores = await retrieve(
736
  retrieval_query, request.collection_name, request.retrieval_mode.value
737
  )