Spaces:
Sleeping
perf: KI-034 retrieval cache + KI-035 reorder FAST_BRAIN_CHAIN
Browse filesKI-034 β Retrieval cache. In-process LRU keyed by
(query_text_normalized, top_k, sorted policy_ids, sorted insurer_slugs).
Cap 256 entries; FIFO/LRU eviction so memory stays bounded across long
sessions. Skips both the Voyage embed call AND the Chroma query +
regulatory/review boost passes on cache hit.
Typical chat session has lots of near-identical follow-ups ("waiting
period?" β "PED waiting period?" β "diabetes waiting period?"). Cache key
is lowercased + stripped so trivial reformatting hits the cache too.
Cache invalidates implicitly on process restart (HF Space rebuild).
Smoke: _cache_key("what is the waiting period?", 5, None, None) ==
_cache_key("WHAT IS the Waiting Period? ", 5, None, None) β True
_cache_get returns ['mock_chunk'] for either form.
KI-035 β FAST_BRAIN_CHAIN reorder. Fast brain serves the latency-sensitive
roles: fact-find, QA, paraphrase, normalize, extract. Throughput-wise
they're all sub-second by content size, so TTFT dominates perceived
latency, not raw capability. Nemotron Nano 30B benches at ~1.6s TTFT vs
Qwen 80B's ~2-3s, so it now sits at position 0; Qwen 80B drops to
position 1 (still serves on Nemotron pool degradation).
Before: Qwen 80B β Nemotron 30B β GPT-OSS β Qwen 122B β DeepSeek β Groq
After: Nemotron 30B β Qwen 80B β GPT-OSS β Qwen 122B β DeepSeek β Groq
Combined effect on fact-find turns:
β’ Each turn typically makes 2 fast-brain calls
(normalize_answer + paraphrase_question) + 1 brain call (the actual
reply). With Nemotron primary on those 2 fast calls, ~1s saved per
turn β meaningful when persona audits average 9-10s p50 today.
KI-025's NIMβGroq 50/50 rotation still applies on top: half of all
fast-brain calls now start at Groq Llama-3.3-70B (sub-1s TTFT on LPU)
regardless of the chain reorder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- backend/providers/nvidia_nim_llm.py +8 -2
- rag/retrieve.py +52 -0
|
@@ -220,8 +220,14 @@ BRAIN_CHAIN = [
|
|
| 220 |
# is the lowest-TTFT free-tier option, so a fast-brain fall-through to it is
|
| 221 |
# still acceptable from a latency-budget standpoint.
|
| 222 |
FAST_BRAIN_CHAIN = [
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
"openai/gpt-oss-120b",
|
| 226 |
"qwen/qwen3.5-122b-a10b",
|
| 227 |
"deepseek-ai/deepseek-v4-flash",
|
|
|
|
| 220 |
# is the lowest-TTFT free-tier option, so a fast-brain fall-through to it is
|
| 221 |
# still acceptable from a latency-budget standpoint.
|
| 222 |
FAST_BRAIN_CHAIN = [
|
| 223 |
+
# KI-035 (2026-05-14) β reordered for latency. Fast brain serves
|
| 224 |
+
# fact-find + QA + paraphrase + normalize + extract: every single one
|
| 225 |
+
# of these is a sub-second job by content size, so the bottleneck IS
|
| 226 |
+
# TTFT, not capability. Nemotron Nano 30B hits ~1.6s; Qwen 80B is
|
| 227 |
+
# ~2-3s. Moved Nemotron to primary; Qwen 80B stays as next fallback so
|
| 228 |
+
# if Nemotron's NIM pool degrades we still get quality.
|
| 229 |
+
"nvidia/nemotron-3-nano-30b-a3b", # ~1.6s TTFT (Reddit bench), NIM
|
| 230 |
+
"qwen/qwen3-next-80b-a3b-instruct", # ~2-3s, NIM
|
| 231 |
"openai/gpt-oss-120b",
|
| 232 |
"qwen/qwen3.5-122b-a10b",
|
| 233 |
"deepseek-ai/deepseek-v4-flash",
|
|
@@ -102,6 +102,49 @@ def _build_chunk(cid: str, doc: str, meta: dict, score: float) -> RetrievedChunk
|
|
| 102 |
)
|
| 103 |
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
async def retrieve(
|
| 106 |
query: str,
|
| 107 |
top_k: int = settings.RAG_TOP_K,
|
|
@@ -121,6 +164,12 @@ async def retrieve(
|
|
| 121 |
This ensures the brain sees regulatory ceilings even when policy
|
| 122 |
chunks dominate raw cosine.
|
| 123 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
embedder = embedder or VoyageEmbeddings()
|
| 125 |
[query_vec] = await embedder.embed([query], input_type="query")
|
| 126 |
|
|
@@ -269,6 +318,9 @@ async def retrieve(
|
|
| 269 |
# Review boost is additive; failure shouldn't kill the main result
|
| 270 |
pass
|
| 271 |
|
|
|
|
|
|
|
|
|
|
| 272 |
return out
|
| 273 |
|
| 274 |
|
|
|
|
| 102 |
)
|
| 103 |
|
| 104 |
|
| 105 |
+
# KI-034 (2026-05-14) β in-process retrieval cache. Keyed by
|
| 106 |
+
# (query_text, top_k, sorted policy_ids, sorted insurer_slugs). Caps at 256
|
| 107 |
+
# entries with FIFO eviction so memory stays bounded across long sessions.
|
| 108 |
+
# Within a single chat session, users frequently rephrase or follow up on the
|
| 109 |
+
# same topic ("what's the waiting period?" β "and the pre-existing diseases
|
| 110 |
+
# waiting period?" β "and the diabetes-specific one?"). When the cache key
|
| 111 |
+
# matches, we skip the Voyage embed call AND the Chroma collection.query() β
|
| 112 |
+
# saving the round-trip + the Voyage 3 RPM tax. Cache invalidates implicitly
|
| 113 |
+
# at process restart (HF Space deploy / reload).
|
| 114 |
+
from collections import OrderedDict as _OrderedDict
|
| 115 |
+
_RETRIEVAL_CACHE: "_OrderedDict[tuple, list]" = _OrderedDict()
|
| 116 |
+
_RETRIEVAL_CACHE_MAX = 256
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _cache_key(
|
| 120 |
+
query: str,
|
| 121 |
+
top_k: int,
|
| 122 |
+
policy_ids: Optional[list[str]],
|
| 123 |
+
insurer_slugs: Optional[list[str]],
|
| 124 |
+
) -> tuple:
|
| 125 |
+
return (
|
| 126 |
+
(query or "").strip().lower(),
|
| 127 |
+
int(top_k),
|
| 128 |
+
tuple(sorted(policy_ids)) if policy_ids else None,
|
| 129 |
+
tuple(sorted(insurer_slugs)) if insurer_slugs else None,
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def _cache_get(key: tuple) -> Optional[list]:
|
| 134 |
+
if key not in _RETRIEVAL_CACHE:
|
| 135 |
+
return None
|
| 136 |
+
# LRU bump
|
| 137 |
+
val = _RETRIEVAL_CACHE.pop(key)
|
| 138 |
+
_RETRIEVAL_CACHE[key] = val
|
| 139 |
+
return val
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _cache_set(key: tuple, value: list) -> None:
|
| 143 |
+
_RETRIEVAL_CACHE[key] = value
|
| 144 |
+
while len(_RETRIEVAL_CACHE) > _RETRIEVAL_CACHE_MAX:
|
| 145 |
+
_RETRIEVAL_CACHE.popitem(last=False) # evict oldest
|
| 146 |
+
|
| 147 |
+
|
| 148 |
async def retrieve(
|
| 149 |
query: str,
|
| 150 |
top_k: int = settings.RAG_TOP_K,
|
|
|
|
| 164 |
This ensures the brain sees regulatory ceilings even when policy
|
| 165 |
chunks dominate raw cosine.
|
| 166 |
"""
|
| 167 |
+
# KI-034 β short-circuit identical-query re-asks via the LRU cache.
|
| 168 |
+
cache_key = _cache_key(query, top_k, policy_ids, insurer_slugs)
|
| 169 |
+
cached = _cache_get(cache_key)
|
| 170 |
+
if cached is not None:
|
| 171 |
+
return cached
|
| 172 |
+
|
| 173 |
embedder = embedder or VoyageEmbeddings()
|
| 174 |
[query_vec] = await embedder.embed([query], input_type="query")
|
| 175 |
|
|
|
|
| 318 |
# Review boost is additive; failure shouldn't kill the main result
|
| 319 |
pass
|
| 320 |
|
| 321 |
+
# KI-034 β populate cache with the FINAL merged result so subsequent
|
| 322 |
+
# identical queries skip Voyage embed + Chroma query + boost passes.
|
| 323 |
+
_cache_set(cache_key, out)
|
| 324 |
return out
|
| 325 |
|
| 326 |
|