""" Streamlit dashboard for AusRegBench benchmark results. Reads results/raw_results.jsonl (per (query, config) judged outcomes). All percentages shown are computed live from that file, never hardcoded, so the dashboard stays correct if the benchmark is re-run. """ import html import json from pathlib import Path import pandas as pd import plotly.express as px import streamlit as st RESULTS_DIR = Path(__file__).resolve().parent.parent / "results" RAW_RESULTS_PATH = RESULTS_DIR / "raw_results.jsonl" GITHUB_URL = "https://github.com/AashishPatnaik/AusRegBench" CONFIG_ORDER = ["naive", "hybrid", "rerank", "kg_augmented", "grounded"] CONFIG_LABELS = { "naive": "Naive", "hybrid": "Hybrid", "rerank": "Rerank", "kg_augmented": "KG-Augmented", "grounded": "Grounded", } BG = "#0A0E1A" CARD_BG = "#131929" BORDER = "#1E2D4A" ACCENT = "#00C2FF" SUCCESS = "#00D68F" DANGER = "#FF4B4B" WARNING = "#F5A623" TEXT_PRIMARY = "#F0F4FF" TEXT_MUTED = "#8B9BB4" CONFIG_COLORS = { "naive": DANGER, "hybrid": WARNING, "rerank": WARNING, "kg_augmented": ACCENT, "grounded": SUCCESS, } TAXONOMY_BUCKETS = [ "correct_and_faithful", "misstated_obligation", "missing_citation", "real_but_irrelevant", "fabricated_citation", ] TAXONOMY_LABELS = { "correct_and_faithful": "Correct & faithful", "misstated_obligation": "Misstated obligation", "missing_citation": "Missing citation", "real_but_irrelevant": "Real but irrelevant", "fabricated_citation": "Fabricated citation", } TAXONOMY_COLORS = { "correct_and_faithful": SUCCESS, "misstated_obligation": WARNING, "missing_citation": TEXT_MUTED, "real_but_irrelevant": ACCENT, "fabricated_citation": DANGER, } st.set_page_config(page_title="AusRegBench", layout="wide") def inject_css() -> None: st.markdown( f""" """, unsafe_allow_html=True, ) @st.cache_data def load_raw_results() -> pd.DataFrame: records = [] with open(RAW_RESULTS_PATH) as f: for line in f: line = line.strip() if not line: continue record = json.loads(line) if "taxonomy_bucket" in record: records.append(record) return pd.DataFrame(records) def build_metrics_table(df: pd.DataFrame) -> pd.DataFrame: rows = [] for config_name in CONFIG_ORDER: config_df = df[df["config_name"] == config_name] total = len(config_df) bucket_counts = config_df["taxonomy_bucket"].value_counts() def pct(bucket: str) -> float: return round(100 * bucket_counts.get(bucket, 0) / total, 1) if total else 0.0 rows.append( { "config_name": config_name, "total": total, "faithful_pct": pct("correct_and_faithful"), "misstated_pct": pct("misstated_obligation"), "missing_pct": pct("missing_citation"), "fabricated_pct": pct("fabricated_citation"), } ) return pd.DataFrame(rows) def metric_card(value: str, label: str, color: str) -> str: return ( f'
' f'
{html.escape(value)}
' f'
{html.escape(label)}
' f"
" ) def config_card(row: pd.Series) -> str: border_color = ( SUCCESS if row["config_name"] == "grounded" else DANGER if row["config_name"] == "naive" else BORDER ) name = CONFIG_LABELS[row["config_name"]] return ( f'
' f'
{html.escape(name)}
' f'
Faithful' f'{row["faithful_pct"]}%
' f'
Misstated' f'{row["misstated_pct"]}%
' f'
Missing citation' f'{row["missing_pct"]}%
' f"
" ) def example_card(row: pd.Series) -> str: rag_answer = html.escape(row["rag_answer"][:400]).replace("\n", "
") reasoning = html.escape(row["layer2_reasoning"]) question = html.escape(row["question"]) return ( f'
' f'MISSTATED OBLIGATION' f'
{question}
' f'
' f'
' f'
RAG ANSWER
{rag_answer}
' f'
' f'
JUDGE REASONING
{reasoning}
' f"
" ) inject_css() raw_df = load_raw_results() metrics_df = build_metrics_table(raw_df) grounded = metrics_df[metrics_df["config_name"] == "grounded"].iloc[0] naive = metrics_df[metrics_df["config_name"] == "naive"].iloc[0] reduction_pct = ( round(100 * (naive["misstated_pct"] - grounded["misstated_pct"]) / naive["misstated_pct"]) if naive["misstated_pct"] else 0 ) # --- Hero --- st.markdown('
AusRegBench
', unsafe_allow_html=True) st.markdown( '
RAG Faithfulness Benchmark for Australian Financial Regulation
', unsafe_allow_html=True, ) hero_cols = st.columns(3) with hero_cols[0]: st.markdown( metric_card(f'{grounded["faithful_pct"]}%', "Grounded config faithful rate", SUCCESS), unsafe_allow_html=True, ) with hero_cols[1]: st.markdown( metric_card(f'{naive["faithful_pct"]}%', "Naive baseline faithful rate", DANGER), unsafe_allow_html=True, ) with hero_cols[2]: st.markdown( metric_card(f"{reduction_pct}%", "Reduction in misstatements", ACCENT), unsafe_allow_html=True, ) st.markdown( f'
Citation-forcing cuts obligation misstatements by ' f'{reduction_pct}% — but still misstates in {grounded["misstated_pct"]}% of cases, ' f"primarily through obligation truncation.
", unsafe_allow_html=True, ) # --- Section 1: results by configuration --- st.markdown('
Results by Configuration
', unsafe_allow_html=True) config_cols = st.columns(5) for col, (_, row) in zip(config_cols, metrics_df.iterrows()): with col: st.markdown(config_card(row), unsafe_allow_html=True) # --- Section 2: key finding chart --- st.markdown( '
Faithful answers by RAG configuration (n=119 per config)
', unsafe_allow_html=True, ) faithful_fig = px.bar( metrics_df, x="faithful_pct", y="config_name", orientation="h", color="config_name", color_discrete_map=CONFIG_COLORS, category_orders={"config_name": CONFIG_ORDER}, ) faithful_fig.update_traces( texttemplate="%{x:.1f}%", textposition="outside" ) faithful_fig.update_yaxes( title="", ticktext=[CONFIG_LABELS[c] for c in CONFIG_ORDER], tickvals=CONFIG_ORDER, ) faithful_fig.update_xaxes(title="Faithful %", range=[0, 100]) faithful_fig.add_vline( x=grounded["faithful_pct"], line_dash="dash", line_color=SUCCESS, annotation_text=f'Grounded: {grounded["faithful_pct"]}%', annotation_position="bottom right", annotation_font_color=SUCCESS, ) faithful_fig.update_layout( template="plotly_dark", plot_bgcolor=CARD_BG, paper_bgcolor=BG, font_color=TEXT_PRIMARY, showlegend=False, margin=dict(l=10, r=10, t=10, b=10), ) st.plotly_chart(faithful_fig, use_container_width=True) # --- Section 3: failure breakdown --- st.markdown( '
Failure taxonomy across configurations
', unsafe_allow_html=True, ) bucket_counts_df = ( raw_df.groupby(["config_name", "taxonomy_bucket"]).size().reset_index(name="count") ) bucket_counts_df["taxonomy_label"] = bucket_counts_df["taxonomy_bucket"].map(TAXONOMY_LABELS) taxonomy_fig = px.bar( bucket_counts_df, x="config_name", y="count", color="taxonomy_bucket", barmode="stack", color_discrete_map=TAXONOMY_COLORS, category_orders={"config_name": CONFIG_ORDER, "taxonomy_bucket": TAXONOMY_BUCKETS}, ) for trace in taxonomy_fig.data: bucket_key = trace.name trace.name = TAXONOMY_LABELS.get(bucket_key, bucket_key) taxonomy_fig.update_xaxes( title="", ticktext=[CONFIG_LABELS[c] for c in CONFIG_ORDER], tickvals=CONFIG_ORDER, ) taxonomy_fig.update_yaxes(title="Queries") taxonomy_fig.update_layout( template="plotly_dark", plot_bgcolor=CARD_BG, paper_bgcolor=BG, font_color=TEXT_PRIMARY, legend_title_text="", margin=dict(l=10, r=10, t=10, b=10), ) st.plotly_chart(taxonomy_fig, use_container_width=True) # --- Section 4: example failures --- st.markdown( '
How obligation misstatements happen
', unsafe_allow_html=True ) st.markdown( '
Three real examples from the naive config — ' "same provision, different ways RAG gets it wrong
", unsafe_allow_html=True, ) misstated_naive = raw_df[ (raw_df["config_name"] == "naive") & (raw_df["taxonomy_bucket"] == "misstated_obligation") ].head(3) for _, row in misstated_naive.iterrows(): st.markdown(example_card(row), unsafe_allow_html=True) # --- Section 5: corpus & methodology --- st.markdown( '
Corpus & Methodology
', unsafe_allow_html=True ) method_cols = st.columns(2) with method_cols[0]: st.markdown( f"""
Corpus
5 sources — Corporations Act 2001, Banking Act 1959, CPS 220, CPS 230, CPS 234
11,613 clause-aware chunks, paragraph IDs preserved
120 hand-verified gold queries
5 stress strata
""", unsafe_allow_html=True, ) with method_cols[1]: st.markdown( f"""
Judge Validation
Cohen's Îș = 0.78 vs. human labels
24/25 agreement on hand-labeled sample
Two-layer pipeline: deterministic citation check + LLM entailment judge
Judge model differs from every generator model under test
""", unsafe_allow_html=True, ) st.markdown("
", unsafe_allow_html=True) st.link_button("View source on GitHub", GITHUB_URL)