from rag.answer import GroundedAnswer from rag.conversation import DeterministicSummariser, InMemoryConversationStore from rag.conversational import ( SMALLTALK_REPLY, ConversationalLoopService, ) from rag.models import ( Evidence, EvidenceDecision, QueryIntent, RetrievalResult, SubjectScope, ) from rag.reasoning import ClarifyReason from rag.routing import CatalogDrugResolver from rag.sections import SectionResolver def _grounded(answer, evidence_text): ev = Evidence("e1", "e1", "prose", evidence_text, 1.0, (), False, False) result = RetrievalResult( EvidenceDecision.ANSWERABLE, "grounded_evidence_available", (ev,), "metformin", "resolved", ) return GroundedAnswer(result, answer, (), False) class FakeAnswers: def __init__(self, answer_text, evidence_text): self._a = answer_text self._e = evidence_text self.calls = [] def answer(self, query, subject_scope, intent, drug_id=None): self.calls.append((query, drug_id)) return _grounded(self._a, self._e) def _service(answers): return ConversationalLoopService( answers=answers, resolver=CatalogDrugResolver({"metformin": {"metformin"}}), section_resolver=SectionResolver(), store=InMemoryConversationStore(), summariser=DeterministicSummariser(), ) def test_smalltalk_answers_socially_without_calling_engine(): answers = FakeAnswers("x", "x") svc = _service(answers) out = svc.answer("c1", "chào bạn", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) assert out.smalltalk is True assert out.answer == SMALLTALK_REPLY assert answers.calls == [] # a greeting is not a drug lookup def test_medical_turn_returns_grounded_answer(): answers = FakeAnswers("Quá mẫn với metformin.", "Quá mẫn với metformin.") svc = _service(answers) out = svc.answer( "c2", "chống chỉ định metformin", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP ) assert out.smalltalk is False assert out.answer == "Quá mẫn với metformin." assert out.grounded is not None def test_followup_inherits_drug_and_passes_it_resolved(): answers = FakeAnswers( "Ở trẻ em điều chỉnh theo cân nặng.", "Ở trẻ em, liều metformin điều chỉnh theo cân nặng.", ) svc = _service(answers) svc.answer("c3", "chống chỉ định metformin", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) out = svc.answer("c3", "còn trẻ em thì sao?", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) assert out.inherited_drug == "metformin" assert out.answer.startswith("Về Metformin:") # The inherited drug is passed already-resolved (not re-resolved from the # rewritten turn text), and the raw follow-up drives section routing. last_query, last_drug_id = answers.calls[-1] assert last_drug_id == "metformin" assert last_query == "còn trẻ em thì sao?" # State carried the drug forward. assert svc._store.load("c3").focus.drug_id == "metformin" def test_confirmation_is_not_fuzzy_matched_to_a_drug(): """'đúng' must not be fuzzy-matched to terbinafin/tretinoin (the did-you-mean loop the reviewer hit); it asks which drug instead.""" answers = FakeAnswers("x", "x") svc = _service(answers) out = svc.answer("cc", "đúng", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) assert out.clarification is not None assert out.clarification.reason == "confirm_without_context" assert answers.calls == [] def test_a_long_sentence_that_names_no_drug_is_not_offered_did_you_mean(): """A full question ('EPO điều trị thiếu máu...') that resolves no drug is answered honestly, not with garbage suggestions from fuzzing the sentence.""" answers = FakeAnswers("x", "x") svc = _service(answers) # catalog holds only metformin out = svc.answer( "cl", "EPO điều trị thiếu máu do hóa trị ung thư liều khởi đầu bao nhiêu", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP, ) assert out.clarification is not None assert out.clarification.reason == "drug_not_supported" assert answers.calls == [] def test_no_close_drug_reports_not_supported(): answers = FakeAnswers("x", "x") svc = _service(answers) # catalog holds only metformin out = svc.answer("c4", "cái này thế nào?", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) assert out.answer is None assert out.clarification is not None # Nothing close to a real drug: honest "not in the formulary", not a guess. assert out.clarification.reason == "drug_not_supported" assert answers.calls == [] def test_typo_offers_did_you_mean_not_silent_resolution(): answers = FakeAnswers("x", "x") svc = _service(answers) # catalog holds only metformin out = svc.answer("c5", "metformim", SubjectScope.HUMAN, QueryIntent.FACT_LOOKUP) # A near-miss is asked about, never auto-resolved on a similarity threshold. assert out.answer is None assert out.clarification is not None assert out.clarification.reason == "did_you_mean" assert "Metformin" in out.clarification.options assert answers.calls == []