Checkpoint frontend UI/UX overhaul and ingestion embed benchmark work
This commit is contained in:
@@ -2,7 +2,9 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from config import Settings
|
||||
from main import create_app
|
||||
from rag.answer import GroundedAnswerService
|
||||
from rag.agent import AgentReply
|
||||
from rag.answer import Citation, GroundedAnswerService
|
||||
from rag.metrics import TRACE_WRITE_FAILED, InMemoryMetrics
|
||||
from rag.models import EvidenceDecision, QueryIntent, RetrievalResult, SubjectScope
|
||||
|
||||
|
||||
@@ -51,3 +53,127 @@ def test_query_requires_structured_scope_and_intent():
|
||||
)
|
||||
response = TestClient(app).post("/v1/rag/query", json={"query": "Liều?"})
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
# --- F-03: the app.state.conversational slot is the new RagAgent, wired live ---
|
||||
|
||||
|
||||
class FakeAgent:
|
||||
"""Stands in for `RagAgent` — proves `routers/rag.py` calls `.handle()`
|
||||
and maps `AgentReply` correctly, not that the agent's own routing logic
|
||||
is correct (that's `test_agent.py`)."""
|
||||
|
||||
def __init__(self, reply: AgentReply) -> None:
|
||||
self._reply = reply
|
||||
self.calls: list[tuple[str, str | None]] = []
|
||||
|
||||
def handle(self, turn: str, conversation_id: str | None = None) -> AgentReply:
|
||||
self.calls.append((turn, conversation_id))
|
||||
return self._reply
|
||||
|
||||
def complete(self, prefix: str, k: int = 8) -> list[str]:
|
||||
return ["Metformin"] if prefix else []
|
||||
|
||||
|
||||
def _citation() -> Citation:
|
||||
return Citation(
|
||||
chunk_id="metformin::lieu::0", printed_page_start=714, printed_page_end=714,
|
||||
physical_page=812,
|
||||
)
|
||||
|
||||
|
||||
def test_query_routes_through_the_agent_when_one_is_configured():
|
||||
agent = FakeAgent(AgentReply(
|
||||
decision="answerable", reason="grounded_evidence_available",
|
||||
answer="Liều 500 mg [1].", citations=(_citation(),),
|
||||
drugs=("metformin",), turn_type="drug_attribute", generated=True,
|
||||
))
|
||||
app = create_app(
|
||||
settings=Settings(),
|
||||
answer_service=GroundedAnswerService(FixedRouting()),
|
||||
conversational=agent,
|
||||
trace_writer=MemoryTraceWriter(),
|
||||
)
|
||||
response = TestClient(app).post("/v1/rag/query", json={
|
||||
"query": "Liều metformin?", "subject_scope": "human",
|
||||
"intent": "fact_lookup", "conversation_id": "c1",
|
||||
})
|
||||
body = response.json()
|
||||
assert response.status_code == 200
|
||||
assert body["decision"] == "answerable"
|
||||
assert body["answer"] == "Liều 500 mg [1]."
|
||||
assert body["resolved_drug_id"] == "metformin"
|
||||
assert len(body["citations"]) == 1
|
||||
assert agent.calls == [("Liều metformin?", "c1")]
|
||||
|
||||
|
||||
def test_query_agent_clarification_is_surfaced_as_the_answer():
|
||||
agent = FakeAgent(AgentReply(
|
||||
decision="clarify", reason="no_drug",
|
||||
clarification="Anh/chị muốn tra thuốc nào?",
|
||||
))
|
||||
app = create_app(
|
||||
settings=Settings(),
|
||||
answer_service=GroundedAnswerService(FixedRouting()),
|
||||
conversational=agent,
|
||||
trace_writer=MemoryTraceWriter(),
|
||||
)
|
||||
response = TestClient(app).post("/v1/rag/query", json={
|
||||
"query": "liều dùng bao nhiêu", "subject_scope": "human",
|
||||
"intent": "fact_lookup",
|
||||
})
|
||||
body = response.json()
|
||||
assert body["decision"] == "clarify"
|
||||
assert body["answer"] == "Anh/chị muốn tra thuốc nào?"
|
||||
assert body["resolved_drug_id"] is None
|
||||
|
||||
|
||||
def test_suggest_delegates_to_the_agent_when_configured():
|
||||
app = create_app(
|
||||
settings=Settings(),
|
||||
conversational=FakeAgent(AgentReply(decision="answerable", reason="x")),
|
||||
trace_writer=MemoryTraceWriter(),
|
||||
)
|
||||
response = TestClient(app).get("/v1/rag/suggest", params={"q": "met"})
|
||||
assert response.json() == {"suggestions": ["Metformin"]}
|
||||
|
||||
|
||||
def test_suggest_with_no_agent_configured_returns_empty():
|
||||
app = create_app(settings=Settings(), trace_writer=MemoryTraceWriter())
|
||||
response = TestClient(app).get("/v1/rag/suggest", params={"q": "met"})
|
||||
assert response.json() == {"suggestions": []}
|
||||
|
||||
|
||||
# --- F-09: trace persistence is fail-open ------------------------------------
|
||||
|
||||
|
||||
class _RaisingTraceWriter:
|
||||
"""Stands in for a Postgres outage: `save()` always raises."""
|
||||
|
||||
def save(self, **fields):
|
||||
raise ConnectionError("could not connect to postgres")
|
||||
|
||||
|
||||
def test_a_trace_write_failure_does_not_turn_a_good_answer_into_a_500():
|
||||
agent = FakeAgent(AgentReply(
|
||||
decision="answerable", reason="grounded_evidence_available",
|
||||
answer="Liều 500 mg [1].", citations=(_citation(),),
|
||||
drugs=("metformin",), turn_type="drug_attribute", generated=True,
|
||||
))
|
||||
metrics = InMemoryMetrics()
|
||||
app = create_app(
|
||||
settings=Settings(),
|
||||
answer_service=GroundedAnswerService(FixedRouting()),
|
||||
conversational=agent,
|
||||
trace_writer=_RaisingTraceWriter(),
|
||||
metrics=metrics,
|
||||
)
|
||||
response = TestClient(app).post("/v1/rag/query", json={
|
||||
"query": "Liều metformin?", "subject_scope": "human", "intent": "fact_lookup",
|
||||
})
|
||||
body = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert body["answer"] == "Liều 500 mg [1]."
|
||||
assert body["trace_id"] # a locally-generated fallback id, still present
|
||||
assert metrics.total(TRACE_WRITE_FAILED) == 1
|
||||
|
||||
Reference in New Issue
Block a user