Add production condition retrieval smoke test

This commit is contained in:
2026-08-11 14:58:28 +07:00
parent 59e6ad2d0d
commit 7ebbe1f309
38 changed files with 3752 additions and 121 deletions
+39 -20
View File
@@ -106,6 +106,10 @@ def _document(payload: dict[str, Any]) -> RetrievalDocument:
part_index=payload.get("part_index"),
part_count=payload.get("part_count"),
context_labels=tuple(payload.get("context_labels") or ()),
section_title=payload.get("section_display_name"),
source_document=payload.get(
"source_document", "Dược thư Quốc gia Việt Nam 2018"
),
)
@@ -193,7 +197,13 @@ class QdrantRetriever:
return [hit for _, hit in hits]
def search_lexical(self, query: str, drug_id: str, limit: int) -> list[SearchHit]:
def search_lexical(
self,
query: str,
drug_id: str,
limit: int,
section_keys: tuple[str, ...] | None = None,
) -> list[SearchHit]:
"""Keyword/BM25-style candidates across ALL of one drug's sections,
ranked by term overlap with `query`.
@@ -216,7 +226,13 @@ class QdrantRetriever:
distinct matched tokens, a transparent stand-in for a real BM25 score
given no term-frequency/IDF statistics are computed here.
"""
from qdrant_client.models import FieldCondition, Filter, MatchText, MatchValue
from qdrant_client.models import (
FieldCondition,
Filter,
MatchAny,
MatchText,
MatchValue,
)
from rag.text import normalize_name
@@ -225,10 +241,15 @@ class QdrantRetriever:
if not tokens:
return []
must = [FieldCondition(key="drug_id", match=MatchValue(value=drug_id))]
if section_keys:
must.append(
FieldCondition(key="section_key", match=MatchAny(any=list(section_keys)))
)
points, _ = self._client.scroll(
collection_name=self._collection_name,
scroll_filter=Filter(
must=[FieldCondition(key="drug_id", match=MatchValue(value=drug_id))],
must=must,
should=[FieldCondition(key="text", match=MatchText(text=t)) for t in tokens],
),
limit=max(limit * 4, 20),
@@ -305,8 +326,10 @@ class QdrantRetriever:
risk — the same "exact match wins, no-match-means-None" philosophy
`find_by_section` already uses, applied across drugs instead of
within one. `limit` caps how many DRUGS are returned (one hit per
drug, first match wins), not how many chunks are scanned — a common
symptom can match far more drugs than is useful to show.
drug. This adapter returns a ranked CHUNK pool; the retrieval service
groups those hits by ``drug_id`` and applies the final entity-level
cap. Keeping that boundary explicit prevents Qdrant scroll order or
chunk count from becoming an accidental drug ranking.
Prose only: a `block_descriptor` chunk carries no real `chi_dinh`
text (its text is built only from metadata per the quarantine
@@ -343,7 +366,6 @@ class QdrantRetriever:
]
)
hits: list[SearchHit] = []
seen_drugs: set[str] = set()
offset = None
while True:
points, offset = self._client.scroll(
@@ -355,19 +377,21 @@ class QdrantRetriever:
)
for point in points:
payload = dict(point.payload or {})
drug_id = payload.get("drug_id")
if drug_id in seen_drugs:
continue
text = normalize_name(payload.get("text", ""))
if not needle_pattern.search(f" {text} "):
match = needle_pattern.search(f" {text} ")
if not match:
continue
seen_drugs.add(drug_id)
hits.append(SearchHit(_document(payload), 1.0))
if len(hits) >= limit:
return hits
# Relevance of one chunk, not popularity of its drug: prefer
# a direct phrase near the start of concise indication text.
# The service later takes MAX per drug, never SUM/count.
words = max(1, len(text.split()))
position = max(0, len(text[: match.start()].split()))
score = 1.0 + 1.0 / (1.0 + position) + 1.0 / (1.0 + words / 40.0)
hits.append(SearchHit(_document(payload), score))
if offset is None:
break
return hits
hits.sort(key=lambda hit: (-hit.score, hit.document.doc_id))
return hits[:limit]
def search_indication(self, query: str, limit: int) -> list[SearchHit]:
"""Dense-vector fallback for `find_by_indication` when no exact
@@ -399,13 +423,8 @@ class QdrantRetriever:
with_payload=True,
)
hits: list[SearchHit] = []
seen_drugs: set[str] = set()
for point in points:
payload = dict(point.payload or {})
drug_id = payload.get("drug_id")
if drug_id in seen_drugs:
continue
seen_drugs.add(drug_id)
hits.append(SearchHit(_document(payload), float(point.score)))
if len(hits) >= limit:
break