Add read-only production runtime audit
This commit is contained in:
@@ -593,6 +593,7 @@ class LlmQueryUnderstander:
|
||||
frame = _apply_broad_condition_cue(
|
||||
frame, turn, self._condition_normalizer
|
||||
)
|
||||
frame = _apply_general_condition_scope(frame, turn)
|
||||
frame = _apply_reverse_relation_cues(frame, turn)
|
||||
section_match = _SECTION_RESOLVER.resolve(turn)
|
||||
frame = _apply_named_drug_cues(
|
||||
@@ -603,7 +604,8 @@ class LlmQueryUnderstander:
|
||||
resolved_section_phrase=(section_match.phrase if section_match else None),
|
||||
)
|
||||
frame = _apply_multi_section_clarify(frame, turn)
|
||||
return _merge_with_prior_frame(frame, prior_frame)
|
||||
frame = _merge_with_prior_frame(frame, prior_frame)
|
||||
return _apply_contextual_candidate_safety(frame, turn, prior_frame)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_id(value: str, shown: dict[str, str]) -> str | None:
|
||||
@@ -754,7 +756,7 @@ def _apply_condition_candidate_cue(
|
||||
"""Keep current medicines subordinate in an explicit condition lookup."""
|
||||
if frame.turn_type == "condition_to_drug" and frame.condition is not None:
|
||||
return frame
|
||||
condition = normalizer.detect_known_alias(turn)
|
||||
condition = frame.condition or normalizer.detect_known_alias(turn)
|
||||
if condition is None:
|
||||
return frame
|
||||
text = f" {normalize_name(turn)} "
|
||||
@@ -767,6 +769,8 @@ def _apply_condition_candidate_cue(
|
||||
" option dieu tri ",
|
||||
" ung vien nao ",
|
||||
" cac ung vien nao ",
|
||||
" co chi dinh lien quan ",
|
||||
" co chi dinh cho ",
|
||||
)
|
||||
if not any(cue in text for cue in candidate_cues):
|
||||
return frame
|
||||
@@ -782,6 +786,80 @@ def _apply_condition_candidate_cue(
|
||||
)
|
||||
|
||||
|
||||
def _apply_general_condition_scope(frame: QueryFrame, turn: str) -> QueryFrame:
|
||||
"""Do not turn a disease name into an unstated patient impairment.
|
||||
|
||||
A general reverse lookup such as ``Viêm gan B mạn dùng thuốc gì?`` names
|
||||
the condition being treated; it does not say that a particular patient has
|
||||
hepatic impairment. The understanding model can otherwise duplicate the
|
||||
same phrase into ``patient_context.hepatic`` and trigger a stage-2 safety
|
||||
review, mixing contraindication/precaution citations into a general
|
||||
indication list. Explicit patient cues keep the full context untouched.
|
||||
"""
|
||||
if frame.turn_type not in {"condition_to_drug", "symptom_to_drug"}:
|
||||
return frame
|
||||
text = f" {normalize_name(turn)} "
|
||||
patient_cues = (
|
||||
" bn ", " benh nhan ", " nguoi benh ", " kem ", " di ung ",
|
||||
" dang dung ", " mang thai ", " cho con bu ", " tuoi ", " kg ",
|
||||
" ckd ", " suy than ", " suy gan ", " child pugh ", " egfr ",
|
||||
" creatinin ", " ast ", " alt ",
|
||||
)
|
||||
if any(cue in text for cue in patient_cues):
|
||||
return frame
|
||||
primary = (
|
||||
frame.condition.normalized_condition
|
||||
if frame.condition is not None
|
||||
else frame.indication
|
||||
)
|
||||
return replace(frame, patient_context=PatientContext(primary_condition=primary))
|
||||
|
||||
|
||||
def _apply_contextual_candidate_safety(
|
||||
frame: QueryFrame,
|
||||
turn: str,
|
||||
prior_frame: QueryFrame | None,
|
||||
) -> QueryFrame:
|
||||
"""Keep ``các thuốc trên`` on the prior condition-to-drug candidate lane.
|
||||
|
||||
This follow-up asks to compare the already retrieved candidates against a
|
||||
new patient constraint. It is not a reverse disease->contraindication
|
||||
lookup, even if the current turn contains words such as ``bệnh thận``.
|
||||
"""
|
||||
if prior_frame is None or prior_frame.turn_type not in {
|
||||
"condition_to_drug", "symptom_to_drug"
|
||||
}:
|
||||
return frame
|
||||
text = f" {normalize_name(turn)} "
|
||||
refers_to_candidates = any(
|
||||
cue in text for cue in (" cac thuoc tren ", " trong cac thuoc tren ")
|
||||
)
|
||||
safety_cue = any(
|
||||
cue in text
|
||||
for cue in (
|
||||
" luu y ", " than trong ", " benh than ", " suy than ",
|
||||
" benh gan ", " suy gan ", " di ung ", " tuong tac ",
|
||||
)
|
||||
)
|
||||
if not (refers_to_candidates and safety_cue):
|
||||
return frame
|
||||
condition = frame.condition or prior_frame.condition
|
||||
return replace(
|
||||
frame,
|
||||
turn_type="condition_to_drug",
|
||||
indication=(
|
||||
condition.normalized_condition
|
||||
if condition is not None
|
||||
else frame.indication or prior_frame.indication
|
||||
),
|
||||
condition=condition,
|
||||
condition_relation=ConditionRelation.INDICATION,
|
||||
needs_clarify=False,
|
||||
clarify_reason=None,
|
||||
quick_replies=(),
|
||||
)
|
||||
|
||||
|
||||
def _apply_broad_condition_cue(
|
||||
frame: QueryFrame, turn: str, normalizer: ConditionNormalizer
|
||||
) -> QueryFrame:
|
||||
@@ -1037,7 +1115,17 @@ def _merge_with_prior_frame(frame: QueryFrame, prior_frame: QueryFrame | None) -
|
||||
indication=indication or prior_frame.indication,
|
||||
condition=condition,
|
||||
patient_context=patient_context,
|
||||
attribute=frame.attribute or prior_frame.attribute,
|
||||
# A current drug-attribute clarify with no attribute is an explicit
|
||||
# ambiguity signal (for example, the user named both "chỉ định" and
|
||||
# "chống chỉ định"). Re-inheriting the previous turn's attribute here
|
||||
# silently picks one of those sections and poisons the frame remembered
|
||||
# for the next quick reply. Other continuation shapes still inherit the
|
||||
# prior slot as before (notably pediatric dosing clarifications).
|
||||
attribute=(
|
||||
frame.attribute
|
||||
if frame.turn_type == "drug_attribute" and frame.needs_clarify
|
||||
else frame.attribute or prior_frame.attribute
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user