Checkpoint frontend UI/UX overhaul and ingestion embed benchmark work

This commit is contained in:
2026-08-06 17:21:21 +07:00
parent 1e8cbdb586
commit a4b8e1c4db
78 changed files with 6761 additions and 654 deletions
+71
View File
@@ -0,0 +1,71 @@
"""Server-derived subject scope.
`routing.py`'s `_scope_gate` abstains outright on `NON_HUMAN` scope. Before
this module, that value came straight from the request body — a caller could
send `{"subject_scope":"human",...}` regardless of the query text, and the
shipped web BFF did exactly that, hard-coded on every request without reading
the message at all (Codex's 2026-08-06 review, F-02). A scope decision must
not be something the caller gets to assert.
`resolve_subject_scope` derives it from the query text and combines that with
whatever the caller claimed by taking the more conservative of the two: a
caller can *narrow* scope (claim `non_human` and have it stick) but can never
*widen* it — a claim of `human` cannot override a server-detected veterinary
turn. This is a corpus-coverage check, not a restriction on what a doctor or
pharmacist is allowed to ask: the book behind this product covers human
drug monographs only, so a query about dosing a dog is out of scope
regardless of who is asking. It has nothing to do with, and must never be
extended into, gatekeeping what kind of *clinical* question a professional
user is allowed to ask (see `[[feedback_no_recommendation_gate]]`/progress
log 2026-08-06 for the removed `QueryIntent.RECOMMENDATION` keyword
detector — this product is for doctors and pharmacists, not lay users, and a
"nên dùng thuốc gì" question from a clinician is exactly what a formulary
reference is for, not something to abstain on).
Deliberately not an LLM call: this is the gate every request passes through,
so it must be cheap, available during a provider outage, and auditable as a
fixed rule instead of a model judgment. It is a keyword heuristic, which
means it has real blind spots (an unusual phrasing can still slip past) — the
same trade-off `rag/agent.py`'s `_looks_non_human` backstop already accepts.
`rag/agent.py` should consolidate onto this module once the new orchestrator
is wired (F-03), instead of keeping a second, narrower keyword list.
"""
from __future__ import annotations
from .models import SubjectScope
from .text import normalize_name
# Phrases that name a non-human recipient. Matched on normalized text (casefold,
# diacritics stripped) so "chó", "CHÓ", "cho chó" all match one entry.
_NON_HUMAN_PHRASES = (
"cho cho", # "cho chó" — normalize_name collapses "chó" -> "cho"
"cho meo",
"cho ga",
"cho vit",
"cho lon",
"cho heo",
"cho bo",
"cho ngua",
"cho de",
"cho cuu",
"thu y",
"vat nuoi",
"gia suc",
"gia cam",
"dong vat",
)
def looks_non_human(query: str) -> bool:
normalized = normalize_name(query)
return any(phrase in normalized for phrase in _NON_HUMAN_PHRASES)
def resolve_subject_scope(query: str, claimed: SubjectScope) -> SubjectScope:
"""The scope that actually gates retrieval: the more conservative of what
the caller claimed and what the query text itself indicates."""
if claimed == SubjectScope.NON_HUMAN or looks_non_human(query):
return SubjectScope.NON_HUMAN
if claimed == SubjectScope.UNKNOWN:
return SubjectScope.UNKNOWN
return SubjectScope.HUMAN