Declines self-harm and harm-to-others requests by intent while answering
toxicology; dense + BM25 search fused by RRF over aria_medical_v2 with
rerank-v5.0-fast; transient retries, Judge JSON mode, startup warm-up;
citations show printed page numbers.
Passages are numbered end to end so every claim is cited inline; follow-up
questions are resolved against the conversation; chapter bibliographies and
index pages are dropped before reranking; the answer badge reports how well
grounded an answer is rather than claiming a GRADE certainty. Dependencies
are pinned.
Drive the graph from the API, stream the answer, bound the endpoint
The API server had its own copy of the pipeline — its own agent order,
routing and failure handling — while the compiled LangGraph app went
unused, and the two had already drifted. The server now drives the graph
and only translates its events into SSE; nodes narrate their own progress,
so the agent trace comes from the code that ran.
The answer streams from the model as it is written instead of being
generated in full, scored, then replayed on a 14ms timer. Same query, warm:
first token 2.5s and complete at 3.5s, against 3.2s/7.2s before. The
refusal path drops to 0.6s. meta follows the prose now, which is invisible
to the reader because the gauge and source rail render on a completed turn.
The judge's retry loop is gone: at temperature 0 on an unchanged prompt it
regenerated an identical answer up to three times and returned it anyway.
Citations are limited to passages the reranker actually stands behind
(ARIA_RELEVANCE_FLOOR, default 0.02) — thin queries were padding the list
wit…
Fix stray markup in answers; report retrieval failures honestly
Two fixes from the recent outage and the rendering reports.
Renderer: thematic breaks (---) printed as a row of hyphens, backslash
escapes printed the backslash (\*All doses...), and <br> emitted inside
table cells printed the tag. All three now render properly, stray inline
tags are dropped while keeping their text, and characters Spectral cannot
draw — subscripts, narrow no-break space, non-breaking hyphen — are folded
to plain equivalents so no tofu boxes land mid-sentence. "eGFR <30" and
existing bold/italic/tables/citations are unaffected.
Backend: a dead vector store reported itself as "failed to reach the
language model", pointing diagnosis at a healthy dependency; it now names
the reference library. An empty retrieval raises instead of handing the
generator an empty context to answer from memory. /api/health probes the
collection and returns 503 unless it is reachable, present and non-empty,
rather than reporting "ok" through a total outage. The dev-only sample
answers can no…
Fix Groq model outage; make provider failures a distinct error state
Groq decommissioned llama-3.3-70b-versatile, which every agent shared via a
single hardcoded get_llm(). Each query returned a 404 that the API then
streamed to the browser as answer prose, so the UI decorated a provider
error with a "MODERATE CERTAINTY" evidence badge and a "grounded reply"
byline.
Model IDs are now centralised in llm/config.py, resolved per role from
ARIA_*_MODEL env vars with live defaults verified against Groq's model list
(generator openai/gpt-oss-120b, everything else openai/gpt-oss-20b). No
Space secrets are required — the defaults work as-is.
Provider failures get their own type and their own SSE `error` event that
carries no confidence, no evidence tier and no citations, and never travels
on the `token` channel the UI renders as an answer. An unreachable guardrail
no longer defaults to "in scope", and a judge that fails or returns
unparseable JSON yields confidence=null, shown as "Not adjudicated", instead
of a placeholder 0.5 on a real gauge.
A startup pref…
Simplify the backend: unify retrieval, fix judge context, package imports, add eval suite