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): self.calls.append(query) 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_names_it_and_rewrites_query(): 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 follow-up was rewritten self-contained before hitting the engine. assert "metformin" in answers.calls[-1] # State carried the drug forward. assert svc._store.load("c3").focus.drug_id == "metformin" 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 == []