hoololi's picture
Upload 7 files
29f893d verified
Raw
History Blame Contribute Delete
30 kB
import json
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st
from datasets import load_dataset
CALCULATIONS_DATASET_ID = "hoololi/llm-calculations"
NEXT_PRIME_DATASET_ID = "hoololi/llm-next-prime"
MODEL_INFO_PATH = Path("model_info_snapshot.json")
MODE_ORDER = ["raw", "prompted", "tool"]
MODE_COLORS = {"raw": "#6EA8FE", "prompted": "#FFB86B", "tool": "#69DB7C"}
st.set_page_config(page_title="LLM & Arithmetic", page_icon="🔢", layout="wide")
@dataclass(frozen=True)
class DatasetConfig:
key: str
title: str
dataset_id: str
description: str
default_stat_view: str
category_label: str
category_col: str
task_label: str = "Task"
DATASETS = {
"calculations": DatasetConfig(
key="calculations",
title="Calculations",
dataset_id=CALCULATIONS_DATASET_ID,
description=(
"This experiment asks LLMs to solve arithmetic operations such as `45 × 23` "
"under raw, prompted, and calculator-tool modes. Each row is one operation, "
"one model, and one mode."
),
default_stat_view="Accuracy by category",
category_label="Operation categories",
category_col="operation_category",
task_label="Operation",
),
"next_prime": DatasetConfig(
key="next_prime",
title="Next Prime",
dataset_id=NEXT_PRIME_DATASET_ID,
description=(
"This experiment asks LLMs to answer questions of the form: "
"`What is the smallest prime number that is strictly greater than n?` "
"Inputs are sampled across magnitude ranges and tested in raw, prompted, "
"and next-prime-tool modes."
),
default_stat_view="Accuracy by magnitude",
category_label="Magnitude groups",
category_col="magnitude_group",
task_label="Input number",
),
}
@st.cache_data(show_spinner=False)
def load_hf_dataset(dataset_id: str) -> pd.DataFrame:
ds = load_dataset(dataset_id, split="train")
df = ds.to_pandas()
return normalize_frame(df)
@st.cache_data(show_spinner=False)
def load_model_info() -> Dict[str, Any]:
if not MODEL_INFO_PATH.exists():
return {}
return json.loads(MODEL_INFO_PATH.read_text(encoding="utf-8"))
def normalize_frame(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
if "operation" not in df.columns and "question" in df.columns:
df["operation"] = df["question"]
if "operation_id" not in df.columns and "case_id" in df.columns:
df["operation_id"] = df["case_id"]
if "operation_category" not in df.columns and "category" in df.columns:
df["operation_category"] = df["category"]
if "correct_result_str" not in df.columns and "correct_result" in df.columns:
df["correct_result_str"] = df["correct_result"].astype("string")
if "extracted_answer_str" not in df.columns and "extracted_answer" in df.columns:
df["extracted_answer_str"] = df["extracted_answer"].astype("string")
for col in ["cost", "latency_seconds", "total_tokens", "input_tokens", "output_tokens"]:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
if "mode" in df.columns:
df["mode"] = pd.Categorical(df["mode"].astype(str), categories=MODE_ORDER, ordered=True)
return df
def sanitize_text(value: Any) -> str:
if value is None or pd.isna(value):
return ""
text = str(value)
text = re.sub(r"'user_id':\s*'[^']+'", "'user_id': '<redacted>'", text)
text = re.sub(r'"user_id"\s*:\s*"[^"]+"', '"user_id": "<redacted>"', text)
text = re.sub(r"user_[A-Za-z0-9]+", "user_<redacted>", text)
return text
def bool_mean_pct(s: pd.Series) -> float:
return float(s.eq(True).mean() * 100)
def selected_models(model_a: str, model_b: str) -> List[str]:
models = [model_a]
if model_b and model_b != model_a:
models.append(model_b)
return models
def sort_magnitude(values: List[Any]) -> List[str]:
def key(v: Any) -> int:
match = re.search(r"10\^(\d+)", str(v))
return int(match.group(1)) if match else 999
return sorted([str(v) for v in values if pd.notna(v)], key=key)
def summarize(df: pd.DataFrame, model_order: Optional[List[str]] = None) -> pd.DataFrame:
if df.empty:
return pd.DataFrame()
summary = (
df.groupby(["model", "mode"], observed=True)
.agg(
rows=("mode", "size"),
accuracy_pct=("final_result_correct", bool_mean_pct),
run_success_pct=("run_success", bool_mean_pct),
avg_total_tokens=("total_tokens", "mean"),
avg_cost=("cost", "mean"),
total_cost=("cost", "sum"),
avg_latency_seconds=("latency_seconds", "mean"),
median_latency_seconds=("latency_seconds", "median"),
)
.reset_index()
)
summary["mode"] = pd.Categorical(summary["mode"].astype(str), categories=MODE_ORDER, ordered=True)
if model_order:
summary["model"] = pd.Categorical(summary["model"], categories=model_order, ordered=True)
summary = summary.sort_values(["model", "mode"])
summary["model"] = summary["model"].astype(str)
summary["mode"] = summary["mode"].astype(str)
return summary.round(4)
def metric_chart(summary: pd.DataFrame, metric: str, yaxis_title: str, percent: bool = False) -> go.Figure:
fig = go.Figure()
if summary.empty or metric not in summary.columns:
return fig
model_order = summary["model"].drop_duplicates().tolist()
for mode in MODE_ORDER:
part = summary[summary["mode"] == mode]
fig.add_bar(
x=part["model"],
y=part[metric],
name=mode,
marker_color=MODE_COLORS[mode],
hovertemplate=f"{yaxis_title}: %{{y:.4g}}<extra>{mode}</extra>",
)
fig.update_layout(
template="plotly_dark",
barmode="group",
height=300,
margin=dict(l=35, r=10, t=20, b=45),
legend=dict(orientation="h", yanchor="top", y=0.99, xanchor="right", x=0.99),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(255,255,255,0.035)",
yaxis_title=yaxis_title,
xaxis_title=None,
)
fig.update_xaxes(categoryorder="array", categoryarray=model_order, tickangle=0)
fig.update_yaxes(gridcolor="rgba(255,255,255,0.12)")
if percent:
fig.update_yaxes(range=[0, 100], tickvals=[0, 20, 40, 60, 80, 100])
return fig
def grouped_accuracy_chart(df: pd.DataFrame, group_col: str, title: str, group_order: Optional[List[str]] = None) -> go.Figure:
if df.empty or group_col not in df.columns:
return go.Figure()
grouped = (
df.groupby(["model", group_col, "mode"], observed=True)
.agg(rows=("mode", "size"), accuracy_pct=("final_result_correct", bool_mean_pct))
.reset_index()
)
grouped[group_col] = grouped[group_col].astype(str)
model_count = grouped["model"].nunique()
if group_order is None:
group_order = sorted(grouped[group_col].dropna().astype(str).unique().tolist())
fig = px.bar(
grouped,
x=group_col,
y="accuracy_pct",
color="mode",
facet_col="model" if model_count > 1 else None,
barmode="group",
category_orders={group_col: group_order, "mode": MODE_ORDER},
color_discrete_map=MODE_COLORS,
hover_data={"rows": True, "accuracy_pct": ":.2f"},
labels={"accuracy_pct": "Accuracy (%)", group_col: ""},
title=title,
)
fig.update_layout(
template="plotly_dark",
height=420 if model_count > 1 else 380,
margin=dict(l=35, r=10, t=45, b=55),
legend=dict(orientation="h", yanchor="top", y=0.98, xanchor="right", x=0.99),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(255,255,255,0.035)",
yaxis_title="Accuracy (%)",
)
fig.update_xaxes(categoryorder="array", categoryarray=group_order)
fig.update_yaxes(range=[0, 100], gridcolor="rgba(255,255,255,0.12)")
fig.for_each_annotation(lambda a: a.update(text=a.text.replace("model=", "")))
return fig
def tool_pipeline_summary(df: pd.DataFrame) -> pd.DataFrame:
tool = df[df["mode"].astype(str) == "tool"].copy()
if tool.empty:
return pd.DataFrame()
tool["final_correct_bool"] = tool["final_result_correct"].eq(True)
tool["tool_correct_bool"] = tool.get("tool_result_correct", pd.Series(False, index=tool.index)).eq(True)
tool["final_or_tool_correct"] = tool["final_correct_bool"] | tool["tool_correct_bool"]
return (
tool.groupby(["model_family", "model"], observed=True)
.agg(
rows=("mode", "size"),
final_answer_accuracy_pct=("final_correct_bool", lambda s: s.mean() * 100),
tool_output_accuracy_pct=("tool_correct_bool", lambda s: s.mean() * 100),
final_or_tool_accuracy_pct=("final_or_tool_correct", lambda s: s.mean() * 100),
run_success_pct=("run_success", bool_mean_pct),
)
.reset_index()
.sort_values(["final_or_tool_accuracy_pct", "final_answer_accuracy_pct"], ascending=False)
.round(3)
)
def tradeoff_chart(df: pd.DataFrame) -> go.Figure:
if df.empty:
return go.Figure()
tradeoff = (
df.groupby(["model_family", "model", "mode"], observed=True)
.agg(
accuracy_pct=("final_result_correct", bool_mean_pct),
success_rate_pct=("run_success", bool_mean_pct),
median_latency=("latency_seconds", "median"),
avg_cost=("cost", "mean"),
)
.reset_index()
.dropna(subset=["avg_cost", "median_latency", "accuracy_pct"])
)
if tradeoff.empty:
return go.Figure()
tradeoff["viable"] = (tradeoff["accuracy_pct"] >= 95) & (tradeoff["success_rate_pct"] >= 95)
tradeoff["label"] = tradeoff["model"].str.split("/").str[-1] + " (" + tradeoff["mode"].astype(str) + ")"
min_positive_cost = tradeoff.loc[tradeoff["avg_cost"] > 0, "avg_cost"].min()
zero_cost_display = (min_positive_cost / 10) if pd.notna(min_positive_cost) else 1e-9
tradeoff["avg_cost_display"] = tradeoff["avg_cost"].where(tradeoff["avg_cost"] > 0, zero_cost_display)
fig = px.scatter(
tradeoff,
x="avg_cost_display",
y="median_latency",
color="accuracy_pct",
symbol="mode",
size="success_rate_pct",
hover_name="label",
hover_data={"model_family": True, "avg_cost": ":.6f", "avg_cost_display": False, "median_latency": ":.3f", "accuracy_pct": ":.2f", "success_rate_pct": ":.2f"},
color_continuous_scale=[[0, "#D73027"], [0.5, "#FEE08B"], [1, "#1A9850"]],
range_color=[50, 100],
symbol_map={"raw": "circle", "prompted": "x", "tool": "square"},
)
viable = tradeoff[tradeoff["viable"]]
fig.add_trace(
go.Scatter(
x=viable["avg_cost_display"],
y=viable["median_latency"],
mode="markers",
marker=dict(symbol="circle-open", size=18, color="white", line=dict(color="black", width=2)),
name="viable ≥95% accuracy/success",
hoverinfo="skip",
)
)
fig.update_xaxes(type="log", title="Average cost per call, USD (zero-cost points shown near axis minimum)", gridcolor="rgba(255,255,255,0.12)")
fig.update_yaxes(type="log", title="Median latency, seconds", gridcolor="rgba(255,255,255,0.12)")
fig.update_layout(
template="plotly_dark",
height=520,
margin=dict(l=35, r=10, t=35, b=45),
title="Cost / latency trade-off, colored by accuracy",
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(255,255,255,0.035)",
legend=dict(orientation="h", yanchor="bottom", y=-0.28, xanchor="center", x=0.5),
)
return fig
def price_per_million(value: Any) -> str:
if value in (None, ""):
return "n/a"
try:
return f"${float(value) * 1_000_000:.2f}/1M tokens"
except (TypeError, ValueError):
return str(value)
def model_card(model: str, model_info: Dict[str, Any]) -> None:
info = model_info.get(model, {})
if not info:
st.caption(f"No OpenRouter snapshot information available for `{model}`.")
return
pricing = info.get("pricing", {}) if isinstance(info.get("pricing", {}), dict) else {}
architecture = info.get("architecture", {}) if isinstance(info.get("architecture", {}), dict) else {}
provider = info.get("top_provider", {}) if isinstance(info.get("top_provider", {}), dict) else {}
supported = info.get("supported_parameters", []) or []
description = (info.get("description") or "").strip().replace("\n", " ")
if len(description) > 260:
description = description[:260].rstrip() + "…"
st.markdown(f"#### {info.get('name') or model}")
st.code(model, language=None)
st.markdown(
f"""
- **Context length:** {info.get('context_length') or provider.get('context_length') or 'n/a'}
- **Max completion tokens:** {provider.get('max_completion_tokens') or 'n/a'}
- **Input price:** {price_per_million(pricing.get('prompt') or pricing.get('input'))}
- **Output price:** {price_per_million(pricing.get('completion') or pricing.get('output'))}
- **Tokenizer:** {architecture.get('tokenizer') or 'n/a'}
- **Supported parameters:** {', '.join(supported[:7]) if supported else 'n/a'}{'…' if len(supported) > 7 else ''}
"""
)
if description:
st.caption(description)
def apply_global_filters(df: pd.DataFrame, config: DatasetConfig, key_prefix: str) -> tuple[pd.DataFrame, List[str]]:
models = sorted(df["model"].dropna().unique().tolist())
categories = sorted(df[config.category_col].dropna().astype(str).unique().tolist()) if config.category_col in df.columns else []
if config.category_col == "magnitude_group":
categories = sort_magnitude(categories)
with st.container(border=True):
c1, c2, c3, c4 = st.columns([1.15, 1.15, 1.45, 1])
with c1:
model_a = st.selectbox("Model A / primary", models, index=0, key=f"{key_prefix}_model_a")
with c2:
model_b = st.selectbox("Model B / compare optional", [""] + models, index=0, key=f"{key_prefix}_model_b")
with c3:
selected_categories = st.multiselect(
config.category_label,
categories,
default=categories,
key=f"{key_prefix}_categories",
)
with c4:
run_error_policy = st.selectbox(
"Run errors in metrics",
["Count as failures", "Exclude from metrics"],
index=0,
key=f"{key_prefix}_run_errors",
)
st.caption("Charts use the selected model names directly. If two models are selected, category/magnitude charts are split by model.")
chosen = selected_models(model_a, model_b)
view = df[df["model"].isin(chosen)].copy()
if selected_categories and config.category_col in view.columns:
view = view[view[config.category_col].astype(str).isin(selected_categories)]
if run_error_policy == "Exclude from metrics" and "run_success" in view.columns:
view = view[view["run_success"].eq(True)]
return view, chosen
def render_dataset_intro(df: pd.DataFrame, config: DatasetConfig) -> None:
st.markdown(f"### {config.title}")
st.markdown(config.description)
st.markdown(f"Dataset: [`{config.dataset_id}`](https://huggingface.co/datasets/{config.dataset_id})")
c1, c2, c3, c4 = st.columns(4)
c1.metric("Rows", f"{len(df):,}")
c2.metric("Tasks", f"{df['operation_id'].nunique():,}" if "operation_id" in df.columns else "n/a")
c3.metric("Models", f"{df['model'].nunique():,}" if "model" in df.columns else "n/a")
c4.metric("Modes", f"{df['mode'].nunique():,}" if "mode" in df.columns else "n/a")
def render_statistics(df: pd.DataFrame, config: DatasetConfig, model_info: Dict[str, Any], key_prefix: str) -> None:
st.caption("Explore aggregate statistics and compare models across raw, prompted, and tool modes.")
view, chosen = apply_global_filters(df, config, key_prefix + "_stats")
summary = summarize(view, chosen)
stat_views = ["Accuracy by category", "Accuracy", "Latency", "Cost", "Tokens", "Tool pipeline", "Cost/latency trade-off"]
if config.key == "next_prime":
stat_views[0] = "Accuracy by magnitude"
default_index = stat_views.index(config.default_stat_view) if config.default_stat_view in stat_views else 0
stat_view = st.selectbox("Statistic view", stat_views, index=default_index, key=f"{key_prefix}_stat_view")
if stat_view in ["Accuracy by category", "Accuracy by magnitude"]:
order = None
if config.category_col == "magnitude_group" and config.category_col in view.columns:
order = sort_magnitude(view[config.category_col].dropna().unique().tolist())
st.plotly_chart(
grouped_accuracy_chart(view, config.category_col, stat_view, order),
width="stretch",
)
elif stat_view == "Accuracy":
st.plotly_chart(metric_chart(summary, "accuracy_pct", "Accuracy (%)", True), width="stretch")
elif stat_view == "Latency":
st.plotly_chart(metric_chart(summary, "avg_latency_seconds", "Average latency, seconds"), width="stretch")
elif stat_view == "Cost":
st.plotly_chart(metric_chart(summary, "avg_cost", "Average cost, USD"), width="stretch")
elif stat_view == "Tokens":
st.plotly_chart(metric_chart(summary, "avg_total_tokens", "Average total tokens"), width="stretch")
elif stat_view == "Tool pipeline":
st.dataframe(tool_pipeline_summary(view), width="stretch", hide_index=True)
elif stat_view == "Cost/latency trade-off":
st.plotly_chart(tradeoff_chart(view), width="stretch")
st.markdown("#### Summary table")
st.dataframe(summary, width="stretch", hide_index=True)
with st.expander("Selected model details from OpenRouter snapshot", expanded=False):
info_cols = st.columns(max(1, len(chosen)))
for col, model in zip(info_cols, chosen):
with col:
model_card(model, model_info)
def quick_filter(df: pd.DataFrame, filter_name: str, config: DatasetConfig) -> pd.DataFrame:
out = df.copy()
if filter_name == "Wrong answers":
out = out[out["final_result_correct"].ne(True)]
elif filter_name == "Correct answers":
out = out[out["final_result_correct"].eq(True)]
elif filter_name == "Run failures":
out = out[out["run_success"].ne(True)]
elif filter_name == "Tool issues":
out = out[
(out["mode"].astype(str) == "tool")
& (
out.get("tool_called", pd.Series(False, index=out.index)).ne(True)
| out.get("tool_result_correct", pd.Series(True, index=out.index)).ne(True)
| out.get("tool_error_type", pd.Series("none", index=out.index)).fillna("none").ne("none")
)
]
elif filter_name == "Tool correct, final failed":
out = out[(out["mode"].astype(str) == "tool") & out.get("tool_result_correct", pd.Series(False, index=out.index)).eq(True) & out["final_result_correct"].ne(True)]
elif filter_name == "Long answers":
lengths = out["answer_text"].fillna("").astype(str).str.len()
out = out[lengths >= lengths.quantile(0.90)]
elif filter_name == "Slowest answers":
out = out[out["latency_seconds"] >= out["latency_seconds"].quantile(0.90)]
elif filter_name == "Fastest answers":
out = out[out["latency_seconds"] <= out["latency_seconds"].quantile(0.10)]
elif filter_name == "Highest cost":
if out["cost"].notna().any():
out = out[out["cost"] >= out["cost"].quantile(0.90)]
elif filter_name == "High magnitude" and config.key == "next_prime":
out = out[out.get("magnitude_group", pd.Series("", index=out.index)).astype(str).isin(["10^4", "10^5"])]
elif filter_name == "Large prime gap" and config.key == "next_prime":
out = out[out.get("distance_to_next_prime", pd.Series(0, index=out.index)) >= out.get("distance_to_next_prime", pd.Series(0, index=out.index)).quantile(0.90)]
return out
def record_label(row: pd.Series) -> str:
status = "✅" if row.get("final_result_correct") == True else "❌"
return f"{status} {row.get('model')} | {row.get('mode')}"
def display_result_panel(row: Optional[pd.Series], title: str) -> None:
st.markdown(f"#### {title}")
if row is None:
st.info("No matching result.")
return
status = "✅ correct" if row.get("final_result_correct") == True else "❌ not correct"
st.markdown(f"**{row.get('model')}** · `{row.get('mode')}` · {status}")
m1, m2, m3 = st.columns(3)
m1.metric("Extracted", str(row.get("extracted_answer_str", row.get("extracted_answer"))))
m2.metric("Latency", f"{row.get('latency_seconds'):.3g}s" if pd.notna(row.get("latency_seconds")) else "n/a")
m3.metric("Cost", f"${row.get('cost'):.6f}" if pd.notna(row.get("cost")) else "n/a")
with st.expander("Diagnostics", expanded=False):
st.json(
{
"run_success": bool(row.get("run_success")) if pd.notna(row.get("run_success")) else None,
"run_error": sanitize_text(row.get("run_error")),
"extraction_error_type": row.get("extraction_error_type"),
"tool_called": row.get("tool_called"),
"tool_result": row.get("tool_result_str", row.get("tool_result")),
"tool_result_correct": row.get("tool_result_correct"),
"tool_error_type": row.get("tool_error_type"),
}
)
st.markdown("**Model answer**")
st.code(sanitize_text(row.get("answer_text")), language=None)
if str(row.get("mode")) == "tool":
with st.expander("Tool details", expanded=False):
st.json(
{
"tool_expression": row.get("tool_expression"),
"tool_result": row.get("tool_result_str", row.get("tool_result")),
"tool_error": sanitize_text(row.get("tool_error")),
"tool_calls": row.get("tool_calls"),
}
)
def render_task_details(case_rows: pd.DataFrame, config: DatasetConfig, key_prefix: str) -> None:
if case_rows.empty:
st.info("No rows for this task.")
return
first = case_rows.iloc[0]
with st.container(border=True):
st.markdown(f"#### {config.task_label}")
st.write(first.get("operation"))
meta = {
"operation_id": first.get("operation_id"),
"category": first.get("operation_category"),
"correct_result": first.get("correct_result_str", first.get("correct_result")),
}
for col in ["input_number", "magnitude_group", "distance_to_next_prime", "input_is_prime"]:
if col in case_rows.columns:
meta[col] = first.get(col)
st.json(meta)
available = case_rows.sort_values(["model", "mode"]).reset_index(drop=True)
labels = available.apply(record_label, axis=1).tolist()
task_id_for_key = str(first.get("operation_id"))
c1, c2 = st.columns(2)
with c1:
left_label = st.selectbox("Left result", labels, index=0, key=f"{key_prefix}_{task_id_for_key}_left_result")
with c2:
right_index = min(1, len(labels) - 1)
right_label = st.selectbox("Right result", labels, index=right_index, key=f"{key_prefix}_{task_id_for_key}_right_result")
left_row = available.iloc[labels.index(left_label)] if labels else None
right_row = available.iloc[labels.index(right_label)] if labels else None
r1, r2 = st.columns(2)
with r1:
display_result_panel(left_row, "Result A")
with r2:
display_result_panel(right_row, "Result B")
def render_details(df: pd.DataFrame, config: DatasetConfig, key_prefix: str) -> None:
st.caption("Pick a task, then compare two model × mode results side by side.")
filters = [
"All",
"Wrong answers",
"Correct answers",
"Run failures",
"Tool issues",
"Tool correct, final failed",
"Long answers",
"Slowest answers",
"Fastest answers",
"Highest cost",
]
if config.key == "next_prime":
filters += ["High magnitude", "Large prime gap"]
c1, c2, c3 = st.columns([1.1, 1.1, 1.8])
previous_filter = st.session_state.get(f"{key_prefix}_active_filter")
with c1:
filter_name = st.selectbox("Quick filter", filters, index=0, key=f"{key_prefix}_quick_filter")
selected_task_key = f"{key_prefix}_selected_task_id"
if previous_filter != filter_name:
st.session_state[f"{key_prefix}_active_filter"] = filter_name
st.session_state.pop(selected_task_key, None)
with c2:
modes = st.multiselect("Modes included", MODE_ORDER, default=MODE_ORDER, key=f"{key_prefix}_detail_modes")
with c3:
search_text = st.text_input("Search task or answer", value="", key=f"{key_prefix}_search")
filtered = quick_filter(df, filter_name, config)
if modes:
filtered = filtered[filtered["mode"].astype(str).isin(modes)]
if search_text.strip():
needle = search_text.strip().lower()
filtered = filtered[
filtered["operation"].fillna("").str.lower().str.contains(needle, regex=False)
| filtered["answer_text"].fillna("").str.lower().str.contains(needle, regex=False)
| filtered["model"].fillna("").str.lower().str.contains(needle, regex=False)
]
st.caption(
f"Quick filter matches {len(filtered):,} rows across "
f"{filtered['operation_id'].nunique() if 'operation_id' in filtered.columns else 0:,} tasks. "
"The comparison area below uses only rows matching the current filter/mode/search selection."
)
if filter_name == "Long answers":
filtered = filtered.assign(_answer_length=filtered["answer_text"].fillna("").astype(str).str.len())
task_ids = (
filtered.groupby("operation_id", observed=True)["_answer_length"]
.max()
.sort_values(ascending=False)
.index.astype(str)
.tolist()
)
elif filter_name == "Slowest answers":
task_ids = (
filtered.groupby("operation_id", observed=True)["latency_seconds"]
.max()
.sort_values(ascending=False)
.index.astype(str)
.tolist()
)
elif filter_name == "Fastest answers":
task_ids = (
filtered.groupby("operation_id", observed=True)["latency_seconds"]
.min()
.sort_values(ascending=True)
.index.astype(str)
.tolist()
)
else:
task_ids = filtered["operation_id"].dropna().astype(str).drop_duplicates().sort_values().tolist()
if not task_ids:
st.info("No tasks match the current filters.")
return
if st.session_state.get(selected_task_key) not in task_ids:
st.session_state[selected_task_key] = task_ids[0]
current_index = task_ids.index(st.session_state[selected_task_key])
n1, n2, n3 = st.columns([0.7, 2.6, 0.7])
with n1:
if st.button("◀ Previous", key=f"{key_prefix}_prev", disabled=current_index == 0):
st.session_state[selected_task_key] = task_ids[current_index - 1]
with n3:
if st.button("Next ▶", key=f"{key_prefix}_next", disabled=current_index == len(task_ids) - 1):
st.session_state[selected_task_key] = task_ids[current_index + 1]
current_index = task_ids.index(st.session_state[selected_task_key])
with n2:
selected_id = st.selectbox(
"Task",
task_ids,
index=current_index,
key=selected_task_key,
)
case_rows = filtered[filtered["operation_id"].astype(str) == selected_id].copy()
render_task_details(case_rows, config, key_prefix)
with st.expander("Rows matching current quick filter", expanded=False):
display_cols = [
"operation_id",
"operation_category",
"model",
"mode",
"operation",
"correct_result_str",
"extracted_answer_str",
"final_result_correct",
"run_success",
"latency_seconds",
"cost",
"tool_error_type",
]
display_cols = [c for c in display_cols if c in filtered.columns]
st.dataframe(filtered[display_cols].head(500), width="stretch", hide_index=True, height=260)
def render_dataset_tab(df: pd.DataFrame, config: DatasetConfig, model_info: Dict[str, Any]) -> None:
render_dataset_intro(df, config)
st.divider()
stats_tab, details_tab = st.tabs(["Statistics", "Detailed results"])
with stats_tab:
render_statistics(df, config, model_info, config.key)
with details_tab:
render_details(df, config, config.key)
st.title("🔢 LLM & Arithmetic")
st.markdown(
"Explore two small, public Hugging Face datasets about LLM behavior on arithmetic-like tasks: "
"standard calculations and next-prime search."
)
with st.spinner("Loading Hugging Face datasets..."):
calculations_df = load_hf_dataset(CALCULATIONS_DATASET_ID)
next_prime_df = load_hf_dataset(NEXT_PRIME_DATASET_ID)
model_info_snapshot = load_model_info()
calc_tab, prime_tab = st.tabs(["Calculations", "Next Prime"])
with calc_tab:
render_dataset_tab(calculations_df, DATASETS["calculations"], model_info_snapshot)
with prime_tab:
render_dataset_tab(next_prime_df, DATASETS["next_prime"], model_info_snapshot)