quantumbit commited on
Commit
7704053
·
verified ·
1 Parent(s): 49fb3b8

Upload folder using huggingface_hub

Browse files
rag_system/cache.py CHANGED
@@ -1,133 +1,274 @@
1
  """
2
- Two-layer cache:
3
- 1. Exact-match hash cache (Redis/in-memory fallback)
4
- 2. Semantic near-duplicate cache using cosine similarity on query embeddings
5
-
6
- Semantic caching prevents re-querying the LLM for paraphrased versions of the
7
- same question - a major cost & latency win in production
8
  """
 
9
 
10
  import hashlib
11
- import json
12
  import logging
13
- import time
14
  from typing import Optional
15
 
16
- from google_crc32c import value
17
  import numpy as np
18
 
19
  from .config import get_settings
20
- from .embeddings import cosine_similarity
21
 
22
  logger = logging.getLogger(__name__)
23
  settings = get_settings()
24
 
25
- # In memory fallback (used when Redis is unavailable)
26
-
27
- class InMemoryCache:
28
- def __init__(self,ttl: int = 3600, max_size: int = 1000):
29
- self._store: dict[str,tuple[str, float]] = {} # Key -> (value, expiry)
30
- self.ttl = ttl
31
- self.max_size = max_size
32
-
33
- def get(self,key: str) -> Optional[str]:
34
- entry = self._store.get(key)
35
- if entry is None:
36
- return None
37
- value, expiry = entry
38
- if time.time() > expiry:
39
- del self._store[key]
40
- return None
41
- return value
42
-
43
- def set(self,key: str, value: str) -> None:
44
- if len(self._store) >= self.max_size:
45
- oldest = next(iter(self._store))
46
- del self._store[oldest]
47
- self._store[key] = (value, time.time() + self.ttl)
48
-
49
- def ping(self) -> bool:
50
- return True
51
-
52
  def _build_redis_client():
53
  try:
54
  import redis
55
- client = redis.from_url(settings.redis_url, decode_responses=True)
 
56
  client.ping()
57
  logger.info("Redis cache connected")
58
  return client
59
- except Exception as e:
60
- logger.warning(f"Redis unavalaible ({e}) - using in-memory cache.")
61
- return InMemoryCache(ttl=settings.cache_ttl_seconds)
62
-
63
- _cache_client = _build_redis_client()
64
-
65
- # Exact match cache
66
- def _cache_key(query: str, collection: str, mode: str, embedding_mode: str) -> str:
67
- payload = f"{query}::{collection}::{mode}::{embedding_mode}"
68
- return "rag:exact:" + hashlib.sha256(payload.encode()).hexdigest()[:32]
69
-
70
- def get_exact(query: str, collection: str, mode: str, embedding_mode: str) -> Optional[dict]:
71
- key = _cache_key(query, collection, mode, embedding_mode)
72
- raw = _cache_client.get(key)
73
- if raw:
74
- logger.debug(f"Exact cache hit: {key[:16]}...")
75
- return json.loads(raw)
76
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- def set_exact(query: str, collection: str, mode: str, embedding_mode: str, value: str) -> None:
79
- key = _cache_key(query, collection, mode, embedding_mode)
 
 
 
80
  serialized = json.dumps(value)
81
- if hasattr(_cache_client,"setex"):
82
- _cache_client.setex(key,settings.cache_ttl_seconds,serialized)
83
- else:
84
- _cache_client.set(key,serialized)
85
-
86
- # Semantic Cache
87
- # stores (embedding, serialized_response) pairs keyed by short hash
88
- _semantic_index: dict[str, list[tuple[list[float],str,dict]]] = {} # mode -> (vec,key,response)
89
-
90
- def get_semantic(query_vec: list[float], embedding_mode: str) -> Optional[dict]:
91
- """Return the cache response if cosine similarity > threshold"""
92
- pool = _semantic_index.get(embedding_mode, [])
93
- best_score = 0.0
94
- best_response = None
95
- for vec, _,response in pool:
96
- score = cosine_similarity(query_vec,vec)
97
- if score > best_score:
98
- best_score = score
99
- best_response = response
100
- if best_score >= settings.semantic_cache_threshold:
101
- logger.info(f"Semantic Cache hit (score={best_score:.3f})")
102
- return best_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  return None
104
 
105
- def set_semantic(query_vec: list[float], query: str, response: dict, embedding_mode: str) -> None:
106
- h = hashlib.md5(query.encode()).hexdigest()[:8]
107
- pool = _semantic_index.setdefault(embedding_mode, [])
108
- pool.append((query_vec, h, response))
109
- if len(pool) > 5000: # cap memory
110
- pool.pop(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  def cache_connected() -> bool:
 
 
113
  try:
114
- return bool(_cache_client.ping())
115
  except Exception:
116
  return False
117
 
 
 
 
 
 
 
 
 
 
 
 
118
  def get_cache_stats() -> dict:
119
- stats = {}
120
- if isinstance(_cache_client, InMemoryCache):
121
- stats["system"] = "in-memory (python dictionary)"
122
- stats["exact_matches_cached"] = len(_cache_client._store)
123
- else:
124
- stats["system"] = "redis"
125
- try:
126
- stats["exact_matches_cached"] = _cache_client.dbsize()
127
- except:
128
- stats["exact_matches_cached"] = "unknown"
129
-
130
- stats["semantic_matches_cached"] = sum(len(v) for v in _semantic_index.values())
 
 
 
 
 
131
  return stats
132
 
133
- print("[cache] Module ready")
 
 
1
  """
2
+ Try Docs cache (Redis-only):
3
+ 1. Exact-match cache in Redis
4
+ 2. Semantic cache using Redis Vector Search (RediSearch)
 
 
 
5
  """
6
+ from __future__ import annotations
7
 
8
  import hashlib
9
+ import json
10
  import logging
11
+ import uuid
12
  from typing import Optional
13
 
 
14
  import numpy as np
15
 
16
  from .config import get_settings
 
17
 
18
  logger = logging.getLogger(__name__)
19
  settings = get_settings()
20
 
21
+ CACHE_EMBEDDING_MODE = "openai-small"
22
+ EXACT_PREFIX = "rag:try:exact:"
23
+ SEMANTIC_INDEX = "rag:try:semantic"
24
+ SEMANTIC_PREFIX = "rag:try:sem:"
25
+ VECTOR_FIELD = "vector"
26
+ PAYLOAD_FIELD = "payload"
27
+ COLLECTION_FIELD = "collection_key"
28
+ PARAMS_FIELD = "params_key"
29
+
30
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def _build_redis_client():
32
  try:
33
  import redis
34
+
35
+ client = redis.from_url(settings.redis_url, decode_responses=False)
36
  client.ping()
37
  logger.info("Redis cache connected")
38
  return client
39
+ except Exception as exc:
40
+ logger.warning("Redis unavailable (%s); caching disabled", exc)
41
+ return None
42
+
43
+
44
+ _client = _build_redis_client()
45
+ _semantic_ready = False
46
+ _semantic_failed = False
47
+
48
+
49
+ def _ensure_semantic_index() -> bool:
50
+ global _semantic_ready, _semantic_failed
51
+ if _semantic_ready:
52
+ return True
53
+ if _semantic_failed or _client is None:
54
+ return False
55
+
56
+ try:
57
+ _client.execute_command("FT.INFO", SEMANTIC_INDEX)
58
+ _semantic_ready = True
59
+ return True
60
+ except Exception:
61
+ pass
62
+
63
+ try:
64
+ dim = str(int(settings.embedding_dimensions_openai))
65
+ _client.execute_command(
66
+ "FT.CREATE",
67
+ SEMANTIC_INDEX,
68
+ "ON",
69
+ "HASH",
70
+ "PREFIX",
71
+ 1,
72
+ SEMANTIC_PREFIX,
73
+ "SCHEMA",
74
+ "query",
75
+ "TEXT",
76
+ COLLECTION_FIELD,
77
+ "TAG",
78
+ PARAMS_FIELD,
79
+ "TAG",
80
+ VECTOR_FIELD,
81
+ "VECTOR",
82
+ "HNSW",
83
+ 6,
84
+ "TYPE",
85
+ "FLOAT32",
86
+ "DIM",
87
+ dim,
88
+ "DISTANCE_METRIC",
89
+ "COSINE",
90
+ )
91
+ _semantic_ready = True
92
+ return True
93
+ except Exception as exc:
94
+ logger.warning("Failed to create Redis vector index: %s", exc)
95
+ _semantic_failed = True
96
+ return False
97
+
98
+
99
+ def _exact_key(query: str, collection_key: str, params_key: str) -> str:
100
+ payload = f"{query}::{collection_key}::{params_key}"
101
+ return EXACT_PREFIX + hashlib.sha256(payload.encode()).hexdigest()[:32]
102
+
103
+
104
+ def _vector_bytes(vec: list[float]) -> bytes:
105
+ return np.array(vec, dtype=np.float32).tobytes()
106
+
107
+
108
+ def _decode(value: bytes | str | None) -> Optional[str]:
109
+ if value is None:
110
+ return None
111
+ if isinstance(value, bytes):
112
+ return value.decode("utf-8")
113
+ return value
114
+
115
+
116
+ # Exact cache
117
+
118
+ def get_exact(query: str, collection_key: str, params_key: str) -> Optional[dict]:
119
+ if _client is None:
120
+ return None
121
+ key = _exact_key(query, collection_key, params_key)
122
+ raw = _client.get(key)
123
+ if not raw:
124
+ return None
125
+ payload = _decode(raw)
126
+ if payload is None:
127
+ return None
128
+ return json.loads(payload)
129
 
130
+
131
+ def set_exact(query: str, collection_key: str, params_key: str, value: dict) -> None:
132
+ if _client is None:
133
+ return
134
+ key = _exact_key(query, collection_key, params_key)
135
  serialized = json.dumps(value)
136
+ _client.set(key, serialized.encode("utf-8"), ex=settings.cache_ttl_seconds)
137
+
138
+
139
+ # Semantic cache
140
+
141
+ def get_semantic(query_vec: list[float], collection_key: str, params_key: str) -> Optional[dict]:
142
+ if _client is None:
143
+ return None
144
+ if not _ensure_semantic_index():
145
+ return None
146
+
147
+ vec = _vector_bytes(query_vec)
148
+ k = 4
149
+ query = (
150
+ f"@{COLLECTION_FIELD}:{{{collection_key}}} "
151
+ f"@{PARAMS_FIELD}:{{{params_key}}}"
152
+ f"=>[KNN {k} @{VECTOR_FIELD} $vec AS score]"
153
+ )
154
+
155
+ try:
156
+ res = _client.execute_command(
157
+ "FT.SEARCH",
158
+ SEMANTIC_INDEX,
159
+ query,
160
+ "PARAMS",
161
+ 2,
162
+ "vec",
163
+ vec,
164
+ "RETURN",
165
+ 2,
166
+ PAYLOAD_FIELD,
167
+ "score",
168
+ "DIALECT",
169
+ 2,
170
+ )
171
+ except Exception as exc:
172
+ logger.warning("Redis semantic search failed: %s", exc)
173
+ return None
174
+
175
+ if not res or res[0] == 0:
176
+ return None
177
+
178
+ best_similarity = 0.0
179
+ best_payload = None
180
+
181
+ for i in range(1, len(res), 2):
182
+ fields = res[i + 1]
183
+ payload = None
184
+ distance = None
185
+ for j in range(0, len(fields), 2):
186
+ name = _decode(fields[j]) or ""
187
+ value = fields[j + 1]
188
+ if name == PAYLOAD_FIELD:
189
+ payload = _decode(value)
190
+ elif name == "score":
191
+ distance = float(_decode(value) or 0)
192
+ if payload is None or distance is None:
193
+ continue
194
+ similarity = 1.0 - distance
195
+ if similarity > best_similarity:
196
+ best_similarity = similarity
197
+ best_payload = payload
198
+
199
+ if best_payload and best_similarity >= settings.semantic_cache_threshold:
200
+ logger.info("Semantic cache hit (score=%.3f)", best_similarity)
201
+ return json.loads(best_payload)
202
+
203
  return None
204
 
205
+
206
+ def set_semantic(
207
+ query_vec: list[float],
208
+ query: str,
209
+ collection_key: str,
210
+ params_key: str,
211
+ response: dict,
212
+ ) -> None:
213
+ if _client is None:
214
+ return
215
+ if not _ensure_semantic_index():
216
+ return
217
+
218
+ key = f"{SEMANTIC_PREFIX}{uuid.uuid4().hex}"
219
+ payload = json.dumps(response)
220
+
221
+ _client.hset(
222
+ key,
223
+ mapping={
224
+ "query": query,
225
+ COLLECTION_FIELD: collection_key,
226
+ PARAMS_FIELD: params_key,
227
+ VECTOR_FIELD: _vector_bytes(query_vec),
228
+ PAYLOAD_FIELD: payload,
229
+ },
230
+ )
231
+ _client.expire(key, settings.cache_ttl_seconds)
232
+
233
 
234
  def cache_connected() -> bool:
235
+ if _client is None:
236
+ return False
237
  try:
238
+ return bool(_client.ping())
239
  except Exception:
240
  return False
241
 
242
+
243
+ def _info_to_dict(raw: list) -> dict:
244
+ info = {}
245
+ if not raw:
246
+ return info
247
+ for i in range(0, len(raw), 2):
248
+ key = _decode(raw[i]) or ""
249
+ info[key] = raw[i + 1]
250
+ return info
251
+
252
+
253
  def get_cache_stats() -> dict:
254
+ if _client is None:
255
+ return {"system": "disabled", "exact_matches_cached": 0, "semantic_matches_cached": 0}
256
+
257
+ stats = {"system": "redis"}
258
+ try:
259
+ stats["exact_matches_cached"] = _client.dbsize()
260
+ except Exception:
261
+ stats["exact_matches_cached"] = "unknown"
262
+
263
+ try:
264
+ info = _client.execute_command("FT.INFO", SEMANTIC_INDEX)
265
+ info_map = _info_to_dict(info)
266
+ num_docs = info_map.get("num_docs", 0)
267
+ stats["semantic_matches_cached"] = int(num_docs) if num_docs is not None else 0
268
+ except Exception:
269
+ stats["semantic_matches_cached"] = "unknown"
270
+
271
  return stats
272
 
273
+
274
+ print("[cache] Module ready")
rag_system/query_engine.py CHANGED
@@ -7,6 +7,7 @@ Core RAG query pipeline:
7
  5. Generate answer (sync or streaming)
8
  6. Return answer + sources
9
  """
 
10
  import logging
11
  import re
12
  import time
@@ -23,7 +24,13 @@ from .retriever import retrieve, detect_query_scope, multi_collection_retrieve
23
  from .vector_store import resolve_embedding_mode_for_collections
24
  from .memory import resolve_standalone_question,trim_history_to_budget, build_lc_messages
25
  from .guardrails import check_query, check_context, redact_pii
26
- from .cache import get_exact,set_exact,get_semantic,set_semantic
 
 
 
 
 
 
27
  from .embeddings import embed_query
28
 
29
  logger = logging.getLogger(__name__)
@@ -53,6 +60,21 @@ def _should_preserve_exact_reference(query: str) -> bool:
53
  """
54
  return bool(_SECTION_REF_RE.search(query) and _SECTION_HINT_RE.search(query))
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  # Query rewriting
57
  async def rewrite_query(query: str) -> str:
58
  """
@@ -170,6 +192,11 @@ async def query(
170
 
171
  collections = request.doc_collections or [request.collection_name]
172
  embedding_mode = resolve_embedding_mode_for_collections(collections, request.embedding_mode)
 
 
 
 
 
173
 
174
  # 1. Input guardrail
175
  guard = check_query(request.query)
@@ -182,8 +209,8 @@ async def query(
182
  )
183
 
184
  # 2. Exact cache check
185
- if settings.cache_enabled:
186
- cached = get_exact(request.query, request.collection_name, request.retrieval_mode, embedding_mode)
187
  if cached:
188
  logger.info(f"Exact cache hit for query: '{request.query}'")
189
  cached["cached"] = True
@@ -191,9 +218,9 @@ async def query(
191
  return QueryResponse(**cached)
192
 
193
  # 3. Embed query for semantic cache + later retrieval
194
- query_vec = await embed_query(request.query, embedding_mode)
195
- if settings.cache_enabled:
196
- semantic_hit = get_semantic(query_vec, embedding_mode)
197
  if semantic_hit:
198
  logger.info(f"Semantic cache hit for query: '{request.query}'")
199
  semantic_hit["cached"] = True
@@ -294,10 +321,12 @@ async def query(
294
  )
295
 
296
  # 9. Cache the result
297
- if settings.cache_enabled:
298
  result_dict = result.model_dump()
299
- set_exact(request.query, request.collection_name, request.retrieval_mode, embedding_mode, result_dict)
300
- set_semantic(query_vec, request.query, result_dict, embedding_mode)
 
 
301
 
302
  return result
303
 
@@ -337,6 +366,9 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
337
  mode_val = request.retrieval_mode.value if hasattr(request.retrieval_mode, "value") else str(request.retrieval_mode)
338
  collections = request.doc_collections or [request.collection_name]
339
  embedding_mode = resolve_embedding_mode_for_collections(collections, request.embedding_mode)
 
 
 
340
 
341
  yield emit("pipeline_start", "in_progress", {
342
  "query": request.query,
@@ -360,9 +392,9 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
360
  yield emit("guardrail_check", "passed", {})
361
 
362
  # --- Cache check ---
363
- query_vec = None
364
- if settings.cache_enabled:
365
- cached = get_exact(request.query, request.collection_name, request.retrieval_mode, embedding_mode)
366
  if cached:
367
  cached["cached"] = True
368
  cached["latency_ms"] = round((time.monotonic() - start) * 1000, 2)
@@ -371,8 +403,8 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
371
  yield "data: [DONE]\n\n"
372
  return
373
 
374
- query_vec = await embed_query(request.query, embedding_mode)
375
- semantic_hit = get_semantic(query_vec, embedding_mode)
376
  if semantic_hit:
377
  semantic_hit["cached"] = True
378
  semantic_hit["latency_ms"] = round((time.monotonic() - start) * 1000, 2)
@@ -508,7 +540,7 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
508
  sources_data = [s.model_dump() for s in sources]
509
 
510
  # Cache result — failure must not crash the stream
511
- if settings.cache_enabled:
512
  try:
513
  result_dict = {
514
  "answer": full_answer,
@@ -519,10 +551,10 @@ async def pipeline_stream_query(request: QueryRequest) -> AsyncIterator[str]:
519
  "latency_ms": latency_ms,
520
  "eval_scores": None,
521
  }
522
- if query_vec is None:
523
- query_vec = await embed_query(request.query, embedding_mode)
524
- set_exact(request.query, request.collection_name, request.retrieval_mode, embedding_mode, result_dict)
525
- set_semantic(query_vec, request.query, result_dict, embedding_mode)
526
  except Exception:
527
  logger.warning("Cache write failed (non-fatal)", exc_info=True)
528
 
 
7
  5. Generate answer (sync or streaming)
8
  6. Return answer + sources
9
  """
10
+ import hashlib
11
  import logging
12
  import re
13
  import time
 
24
  from .vector_store import resolve_embedding_mode_for_collections
25
  from .memory import resolve_standalone_question,trim_history_to_budget, build_lc_messages
26
  from .guardrails import check_query, check_context, redact_pii
27
+ from .cache import (
28
+ CACHE_EMBEDDING_MODE,
29
+ get_exact,
30
+ set_exact,
31
+ get_semantic,
32
+ set_semantic,
33
+ )
34
  from .embeddings import embed_query
35
 
36
  logger = logging.getLogger(__name__)
 
60
  """
61
  return bool(_SECTION_REF_RE.search(query) and _SECTION_HINT_RE.search(query))
62
 
63
+
64
+ def _is_try_docs_scope(collections: list[str]) -> bool:
65
+ prefix = settings.try_docs_prefix
66
+ return bool(collections) and all(c.startswith(prefix) for c in collections)
67
+
68
+
69
+ def _cache_collection_key(collections: list[str]) -> str:
70
+ raw = "|".join(sorted(collections))
71
+ return hashlib.sha1(raw.encode()).hexdigest()[:16]
72
+
73
+
74
+ def _cache_params_key(mode: str, top_k: Optional[int]) -> str:
75
+ k = top_k if top_k is not None else settings.top_k_rerank
76
+ return f"{mode}:{k}"
77
+
78
  # Query rewriting
79
  async def rewrite_query(query: str) -> str:
80
  """
 
192
 
193
  collections = request.doc_collections or [request.collection_name]
194
  embedding_mode = resolve_embedding_mode_for_collections(collections, request.embedding_mode)
195
+ mode_val = request.retrieval_mode.value if hasattr(request.retrieval_mode, "value") else str(request.retrieval_mode)
196
+ cache_allowed = settings.cache_enabled and _is_try_docs_scope(collections)
197
+ cache_collection_key = _cache_collection_key(collections)
198
+ cache_params_key = _cache_params_key(mode_val, request.top_k)
199
+ cache_query_vec = None
200
 
201
  # 1. Input guardrail
202
  guard = check_query(request.query)
 
209
  )
210
 
211
  # 2. Exact cache check
212
+ if cache_allowed:
213
+ cached = get_exact(request.query, cache_collection_key, cache_params_key)
214
  if cached:
215
  logger.info(f"Exact cache hit for query: '{request.query}'")
216
  cached["cached"] = True
 
218
  return QueryResponse(**cached)
219
 
220
  # 3. Embed query for semantic cache + later retrieval
221
+ if cache_allowed:
222
+ cache_query_vec = await embed_query(request.query, CACHE_EMBEDDING_MODE)
223
+ semantic_hit = get_semantic(cache_query_vec, cache_collection_key, cache_params_key)
224
  if semantic_hit:
225
  logger.info(f"Semantic cache hit for query: '{request.query}'")
226
  semantic_hit["cached"] = True
 
321
  )
322
 
323
  # 9. Cache the result
324
+ if cache_allowed:
325
  result_dict = result.model_dump()
326
+ set_exact(request.query, cache_collection_key, cache_params_key, result_dict)
327
+ if cache_query_vec is None:
328
+ cache_query_vec = await embed_query(request.query, CACHE_EMBEDDING_MODE)
329
+ set_semantic(cache_query_vec, request.query, cache_collection_key, cache_params_key, result_dict)
330
 
331
  return result
332
 
 
366
  mode_val = request.retrieval_mode.value if hasattr(request.retrieval_mode, "value") else str(request.retrieval_mode)
367
  collections = request.doc_collections or [request.collection_name]
368
  embedding_mode = resolve_embedding_mode_for_collections(collections, request.embedding_mode)
369
+ cache_allowed = settings.cache_enabled and _is_try_docs_scope(collections)
370
+ cache_collection_key = _cache_collection_key(collections)
371
+ cache_params_key = _cache_params_key(mode_val, request.top_k)
372
 
373
  yield emit("pipeline_start", "in_progress", {
374
  "query": request.query,
 
392
  yield emit("guardrail_check", "passed", {})
393
 
394
  # --- Cache check ---
395
+ cache_query_vec = None
396
+ if cache_allowed:
397
+ cached = get_exact(request.query, cache_collection_key, cache_params_key)
398
  if cached:
399
  cached["cached"] = True
400
  cached["latency_ms"] = round((time.monotonic() - start) * 1000, 2)
 
403
  yield "data: [DONE]\n\n"
404
  return
405
 
406
+ cache_query_vec = await embed_query(request.query, CACHE_EMBEDDING_MODE)
407
+ semantic_hit = get_semantic(cache_query_vec, cache_collection_key, cache_params_key)
408
  if semantic_hit:
409
  semantic_hit["cached"] = True
410
  semantic_hit["latency_ms"] = round((time.monotonic() - start) * 1000, 2)
 
540
  sources_data = [s.model_dump() for s in sources]
541
 
542
  # Cache result — failure must not crash the stream
543
+ if cache_allowed:
544
  try:
545
  result_dict = {
546
  "answer": full_answer,
 
551
  "latency_ms": latency_ms,
552
  "eval_scores": None,
553
  }
554
+ if cache_query_vec is None:
555
+ cache_query_vec = await embed_query(request.query, CACHE_EMBEDDING_MODE)
556
+ set_exact(request.query, cache_collection_key, cache_params_key, result_dict)
557
+ set_semantic(cache_query_vec, request.query, cache_collection_key, cache_params_key, result_dict)
558
  except Exception:
559
  logger.warning("Cache write failed (non-fatal)", exc_info=True)
560
 
rag_system/vector_store.py CHANGED
@@ -3,6 +3,7 @@ import json
3
  import logging
4
  import time
5
  import os
 
6
  from pathlib import Path
7
  from typing import Optional
8
 
@@ -225,7 +226,13 @@ def similarity_search_with_scores(
225
  if store is None:
226
  raise ValueError(f"Collection '{collection}' not loaded. Ingest documents first.")
227
  _last_used[collection] = time.time()
228
- return store.similarity_search_with_relevance_scores(query, k=k)
 
 
 
 
 
 
229
 
230
  def get_store(collection: str = "default") -> Optional[FAISS]:
231
  return _stores.get(collection)
 
3
  import logging
4
  import time
5
  import os
6
+ import warnings
7
  from pathlib import Path
8
  from typing import Optional
9
 
 
226
  if store is None:
227
  raise ValueError(f"Collection '{collection}' not loaded. Ingest documents first.")
228
  _last_used[collection] = time.time()
229
+ with warnings.catch_warnings():
230
+ warnings.filterwarnings(
231
+ "ignore",
232
+ message=r"Relevance scores must be between 0 and 1, got.*",
233
+ category=UserWarning,
234
+ )
235
+ return store.similarity_search_with_relevance_scores(query, k=k)
236
 
237
  def get_store(collection: str = "default") -> Optional[FAISS]:
238
  return _stores.get(collection)