106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
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 == []
|