Wire the guarded conversational RAG answer layer end-to-end

This commit is contained in:
2026-08-05 14:33:13 +07:00
parent 834d9e51b0
commit ef08b4929e
127 changed files with 37921 additions and 169 deletions
+53
View File
@@ -0,0 +1,53 @@
from fastapi.testclient import TestClient
from config import Settings
from main import create_app
from rag.answer import GroundedAnswerService
from rag.models import EvidenceDecision, QueryIntent, RetrievalResult, SubjectScope
class FixedRouting:
def retrieve(self, query, subject_scope, intent):
assert query == "Liều thuốc?"
assert subject_scope == SubjectScope.HUMAN
assert intent == QueryIntent.FACT_LOOKUP
return RetrievalResult(EvidenceDecision.ABSTAIN, "drug_not_resolved")
class MemoryTraceWriter:
def __init__(self):
self.rows = []
def save(self, **fields):
self.rows.append(fields)
return "trace-1"
def test_health_and_fail_closed_rag_response_are_traced():
traces = MemoryTraceWriter()
app = create_app(
settings=Settings(),
answer_service=GroundedAnswerService(FixedRouting()),
trace_writer=traces,
)
client = TestClient(app)
assert client.get("/health").json() == {"status": "ok"}
response = client.post("/v1/rag/query", json={
"query": "Liều thuốc?",
"subject_scope": "human",
"intent": "fact_lookup",
})
assert response.status_code == 200
assert response.json()["decision"] == "abstain"
assert response.json()["trace_id"] == "trace-1"
assert traces.rows[0]["reason"] == "drug_not_resolved"
def test_query_requires_structured_scope_and_intent():
app = create_app(
settings=Settings(),
answer_service=GroundedAnswerService(FixedRouting()),
trace_writer=MemoryTraceWriter(),
)
response = TestClient(app).post("/v1/rag/query", json={"query": "Liều?"})
assert response.status_code == 422