vinod-anbalagan commited on
Commit
cd30a52
·
verified ·
1 Parent(s): 902c7f4

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -356
app.py DELETED
@@ -1,356 +0,0 @@
1
- """Multicultural Riddles Benchmark — human evaluation Space.
2
-
3
- Self-contained Streamlit app for the Cohere Labs lead team. Recomputes the
4
- coverage tracker and the floor-vs-ceiling leaderboard directly from Henry's
5
- flat file on every load, so dropping a newer
6
- `data/model_riddle_results_flat.csv` (and pushing it to the Space) refreshes
7
- every number and chart with no code change.
8
-
9
- Single source of truth for scores = the flat file. Every non-pending row
10
- carries a score (human 0-3, or auto: exact_match=3, empty/placeholder=0,
11
- idk=1). The leaderboard is computed on COMPLETE communities only, using those
12
- scores for every row, with pending rows DROPPED (never judge-filled) — so
13
- every value traces to the flat file with zero assumptions. Provisional until
14
- coverage completes; every view is labelled N/44.
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- from datetime import datetime
20
- from pathlib import Path
21
-
22
- import pandas as pd
23
- import plotly.graph_objects as go
24
- import streamlit as st
25
-
26
- DATA = Path(__file__).parent / "data"
27
- FLAT = DATA / "model_riddle_results_flat.csv"
28
- AUTO = DATA / "results_tidy.parquet" # our heuristic-judge output (static)
29
-
30
- TOTAL_COMMUNITIES = 44
31
- EXPECTED_MODELS = 24
32
- RIDDLES_PER_COMMUNITY = 100
33
- THIN_THRESHOLD = 0.20
34
- STRICT_SCORE = 3.0
35
- LENIENT_SCORE = 2.0
36
- EXCLUDED_RIDDLES = {"spanish_cuba_history_016"} # Cuba: wrong v0 text, redone in v2
37
-
38
- # Henry's model_name -> our display name (verified 24/24).
39
- MODEL_NAME_MAP = {
40
- "Amazon-Nova-Lite": "Amazon Nova Lite v1",
41
- "Amazon-Nova-Micro": "Amazon Nova Micro v1",
42
- "Deepseek-v4-flash": "DeepSeek V4 Flash",
43
- "EuroLLM-Instruct": "EuroLLM-22B-Instruct-2512",
44
- "GPT5.5-high": "GPT5.5 [high]",
45
- "GPT5.5-none": "GPT5.5 [none]",
46
- "Gemini-2.5-Flash": "Gemini 2.5 Flash",
47
- "Gemini-2.5-Flash-Lite": "Gemini 2.5 Flash Lite",
48
- "Mistral-Large-3": "Mistral Large 3",
49
- "Olmo-3-32B-Think": "OLMo-3-32B-Think",
50
- "Qwen-3.5-0.8B": "Qwen/Qwen3.5-0.8B",
51
- "Qwen-SEA-LION-v4.5-27B-IT": "Qwen-SEA-LION-v4.5-27B-IT",
52
- "Qwen3.5-27B": "Qwen3.5 27B",
53
- "claude-opus-4-7": "claude-opus-4-7 [none]",
54
- "claude-opus-4-7-thinking": "claude-opus-4-7 [thinking]",
55
- "command-a-plus-05-2026": "Cohere Command A Plus",
56
- "deepseek-v4-pro": "deepseek-v4-pro",
57
- "deepseek-v4-pro-thinking": "deepseek-v4-pro-thinking",
58
- "gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
59
- "gemma-4-31b": "Gemma 4 31B IT",
60
- "tiny-aya-earth": "Tiny-Aya earth",
61
- "tiny-aya-fire": "Tiny-Aya fire",
62
- "tiny-aya-global": "Tiny-Aya global",
63
- "tiny-aya-water": "Tiny-Aya water",
64
- }
65
-
66
- FLOOR_COLOR = "#9aa0a6"
67
- CEIL_COLOR = "#3b6fb6"
68
-
69
-
70
- @st.cache_data(show_spinner="Loading Henry's flat file…")
71
- def load():
72
- """Read + join once per file version; cache keyed on file mtime."""
73
- h = pd.read_csv(FLAT, low_memory=False)
74
- h = h[~h["riddle_id"].isin(EXCLUDED_RIDDLES)].copy()
75
- h["model_display"] = h["model_name"].map(MODEL_NAME_MAP)
76
- unmapped = sorted(h.loc[h["model_display"].isna(), "model_name"].dropna().unique())
77
- if unmapped:
78
- st.error(f"Unmapped model_name(s) in flat file: {unmapped}. "
79
- "Numbers withheld until the name map is updated.")
80
- st.stop()
81
-
82
- auto = pd.read_parquet(AUTO)[["riddle_id", "model_display", "judge_verdict"]]
83
- h["is_human"] = h["scoring_method"].eq("human_eval")
84
- h["is_pending"] = h["scoring_method"].eq("pending_human_eval")
85
- h["is_correct"] = h["score"].eq(STRICT_SCORE)
86
- h["is_correct_lenient"] = h["score"].ge(LENIENT_SCORE)
87
- m = h.merge(auto, on=["riddle_id", "model_display"], how="left")
88
- mtime = datetime.fromtimestamp(FLAT.stat().st_mtime).strftime("%Y-%m-%d %H:%M")
89
- return m, mtime
90
-
91
-
92
- def coverage(h: pd.DataFrame) -> pd.DataFrame:
93
- g = h.groupby("community")
94
- cov = pd.DataFrame({
95
- "human_done": g["is_human"].sum().astype(int),
96
- "pending": g["is_pending"].sum().astype(int),
97
- })
98
- cov["human_eligible"] = cov["human_done"] + cov["pending"]
99
- cov["pct_complete"] = (
100
- cov["human_done"] / cov["human_eligible"].where(cov["human_eligible"] > 0) * 100
101
- ).round(1)
102
-
103
- def status(r):
104
- if r["human_done"] == 0:
105
- return "not_started"
106
- if r["human_eligible"] == 0 or r["pct_complete"] >= 99:
107
- return "complete"
108
- if r["pct_complete"] < THIN_THRESHOLD * 100:
109
- return "thin"
110
- return "partial"
111
-
112
- cov["status"] = cov.apply(status, axis=1)
113
- return cov.sort_values(["status", "pct_complete"])
114
-
115
-
116
- def leaderboard(m: pd.DataFrame, communities: set, strict: bool) -> pd.DataFrame:
117
- """Floor (heuristic) vs ceiling (human/Henry) on complete communities.
118
-
119
- Ceiling uses Henry's score for every row, pending dropped (never filled).
120
- Floor is the heuristic judge on the very same rows.
121
- """
122
- col = "is_correct" if strict else "is_correct_lenient"
123
- sub = m[m["community"].isin(communities) & ~m["is_pending"]]
124
- ceil = sub.groupby("model_display")[col].mean().mul(100)
125
- floor = (
126
- sub.assign(auto=sub["judge_verdict"].eq("correct"))
127
- .groupby("model_display")["auto"].mean().mul(100)
128
- )
129
- n = sub.groupby("model_display").size()
130
- human_share = sub.groupby("model_display")["is_human"].mean().mul(100)
131
- lb = pd.DataFrame({
132
- "Human (ceiling) %": ceil, "Heuristic (floor) %": floor,
133
- "n": n, "human-scored %": human_share.round(0),
134
- }).sort_values("Human (ceiling) %", ascending=False)
135
- lb.insert(0, "rank", range(1, len(lb) + 1))
136
- return lb.round(1)
137
-
138
-
139
- def community_difficulty(m: pd.DataFrame, communities: set, strict: bool) -> pd.DataFrame:
140
- """Mean accuracy across all models per community (how hard each culture is)."""
141
- col = "is_correct" if strict else "is_correct_lenient"
142
- sub = m[m["community"].isin(communities) & ~m["is_pending"]]
143
- human = sub.groupby("community")[col].mean().mul(100)
144
- floor = (
145
- sub.assign(a=sub["judge_verdict"].eq("correct"))
146
- .groupby("community")["a"].mean().mul(100)
147
- )
148
- d = pd.DataFrame({"Human %": human, "Heuristic %": floor})
149
- return d.sort_values("Human %").round(1)
150
-
151
-
152
- def community_kappa(m: pd.DataFrame, communities: set) -> tuple:
153
- """Cohen's kappa between heuristic judge and human, per community.
154
-
155
- Computed on human-scored rows only (where both labels exist). Measures how
156
- well strict string-matching agrees with human judgement; low kappa flags
157
- communities where the auto-judge is unreliable (usually non-Latin scripts).
158
- """
159
- r = m[m["is_human"] & m["community"].isin(communities) & m["judge_verdict"].notna()].copy()
160
- r["h"] = r["score"].eq(STRICT_SCORE)
161
- r["j"] = r["judge_verdict"].eq("correct")
162
-
163
- def kappa(x):
164
- po = (x["h"] == x["j"]).mean()
165
- ph, pj = x["h"].mean(), x["j"].mean()
166
- pe = ph * pj + (1 - ph) * (1 - pj)
167
- return float("nan") if pe == 1 else (po - pe) / (1 - pe)
168
-
169
- per = r.groupby("community").apply(kappa).rename("kappa").sort_values()
170
- overall = kappa(r)
171
- misses = int((~r["j"] & r["h"]).sum()) # human-correct the judge missed
172
- overcredit = int((r["j"] & ~r["h"]).sum()) # judge-correct humans rejected
173
- return per.round(3), overall, misses, overcredit, len(r)
174
-
175
-
176
- def hbar(series: pd.Series, xlabel: str, color: str, fmt: str = ".1f") -> go.Figure:
177
- fig = go.Figure(go.Bar(
178
- x=series.values, y=series.index, orientation="h",
179
- marker_color=color,
180
- hovertemplate="%{y}<br>%{x:" + fmt + "}<extra></extra>"))
181
- fig.update_layout(
182
- height=max(420, 24 * len(series)), margin=dict(l=10, r=10, t=10, b=40),
183
- xaxis_title=xlabel, plot_bgcolor="white", font=dict(size=12))
184
- fig.update_xaxes(showgrid=True, gridcolor="#eef0f2", zeroline=False)
185
- return fig
186
-
187
-
188
- def difficulty_fig(d: pd.DataFrame) -> go.Figure:
189
- fig = go.Figure()
190
- for i, (c, r) in enumerate(d.iterrows()):
191
- fig.add_trace(go.Scatter(
192
- x=[r["Heuristic %"], r["Human %"]], y=[c, c], mode="lines",
193
- line=dict(color="#d0d3d8", width=3), showlegend=False, hoverinfo="skip"))
194
- fig.add_trace(go.Scatter(
195
- x=d["Heuristic %"], y=d.index, mode="markers", name="Heuristic (floor)",
196
- marker=dict(color=FLOOR_COLOR, size=10),
197
- hovertemplate="%{y}<br>heuristic %{x:.1f}%<extra></extra>"))
198
- fig.add_trace(go.Scatter(
199
- x=d["Human %"], y=d.index, mode="markers", name="Human (ceiling)",
200
- marker=dict(color=CEIL_COLOR, size=10),
201
- hovertemplate="%{y}<br>human %{x:.1f}%<extra></extra>"))
202
- fig.update_layout(
203
- height=max(420, 26 * len(d)), margin=dict(l=10, r=10, t=10, b=40),
204
- xaxis_title="Mean accuracy across models (%)",
205
- legend=dict(orientation="h", y=1.05), plot_bgcolor="white", font=dict(size=12))
206
- fig.update_xaxes(showgrid=True, gridcolor="#eef0f2", zeroline=False)
207
- return fig
208
-
209
-
210
- def dumbbell(lb: pd.DataFrame, strict: bool) -> go.Figure:
211
- d = lb.sort_values("Human (ceiling) %")
212
- fig = go.Figure()
213
- for i, (model, r) in enumerate(d.iterrows()):
214
- fig.add_trace(go.Scatter(
215
- x=[r["Heuristic (floor) %"], r["Human (ceiling) %"]], y=[model, model],
216
- mode="lines", line=dict(color="#d0d3d8", width=3),
217
- showlegend=False, hoverinfo="skip"))
218
- fig.add_trace(go.Scatter(
219
- x=d["Heuristic (floor) %"], y=d.index, mode="markers", name="Heuristic (floor)",
220
- marker=dict(color=FLOOR_COLOR, size=11),
221
- hovertemplate="%{y}<br>heuristic %{x:.1f}%<extra></extra>"))
222
- fig.add_trace(go.Scatter(
223
- x=d["Human (ceiling) %"], y=d.index, mode="markers", name="Human (ceiling)",
224
- marker=dict(color=CEIL_COLOR, size=11),
225
- hovertemplate="%{y}<br>human %{x:.1f}%<extra></extra>"))
226
- lab = "strict (top score)" if strict else "lenient (score ≥ 2)"
227
- fig.update_layout(
228
- height=max(420, 26 * len(d)), margin=dict(l=10, r=10, t=10, b=40),
229
- xaxis_title=f"Accuracy (%), {lab}", legend=dict(orientation="h", y=1.05),
230
- plot_bgcolor="white", font=dict(size=13))
231
- fig.update_xaxes(showgrid=True, gridcolor="#eef0f2", zeroline=False)
232
- return fig
233
-
234
-
235
- # ------------------------------- UI -------------------------------
236
- st.set_page_config(page_title="Multicultural Riddles — Human Eval",
237
- page_icon="🧩", layout="wide")
238
-
239
- if not FLAT.exists():
240
- st.error("data/model_riddle_results_flat.csv not found in the Space.")
241
- st.stop()
242
-
243
- m, mtime = load()
244
- cov = coverage(m.drop_duplicates(["community", "riddle_id", "model_display"]))
245
- complete = set(cov[cov.status == "complete"].index)
246
- counts = cov.status.value_counts().to_dict()
247
-
248
- st.title("🧩 Multicultural Riddles Benchmark — Human Evaluation")
249
- st.caption(
250
- f"Provisional leaderboard, live from Henry's flat file (updated {mtime}). "
251
- "Every score traces to the flat file; pending rows are dropped, never "
252
- "guessed. Numbers can still shift as human coverage completes.")
253
-
254
- c1, c2, c3, c4 = st.columns(4)
255
- c1.metric("Communities complete", f"{len(complete)} / {TOTAL_COMMUNITIES}")
256
- c2.metric("Models", m["model_display"].nunique())
257
- c3.metric("Human-scored rows", f"{int(m['is_human'].sum()):,}")
258
- c4.metric("Pending", f"{int(m['is_pending'].sum()):,}")
259
-
260
- strict = st.radio("Scoring", ["Strict (top score = 3)", "Lenient (score ≥ 2)"],
261
- horizontal=True) == "Strict (top score = 3)"
262
-
263
- if not complete:
264
- st.warning("No community has complete human evaluation yet.")
265
- st.stop()
266
-
267
- lb = leaderboard(m, complete, strict)
268
-
269
- tab_lb, tab_diff, tab_kappa, tab_cov, tab_map = st.tabs(
270
- ["🏆 Leaderboard", "🌍 Community difficulty", "🤝 Judge vs human",
271
- "📊 Coverage", "🗺️ Coverage map"])
272
-
273
- with tab_lb:
274
- st.subheader(f"Floor → ceiling (N={len(complete)}/{TOTAL_COMMUNITIES} "
275
- "complete communities, provisional)")
276
- st.plotly_chart(dumbbell(lb, strict), use_container_width=True)
277
- st.caption("Grey = heuristic auto-judge (lower bound). Blue = human "
278
- "judgement (upper bound). The gap is the lift from strict "
279
- "string-matching to human scoring.")
280
- st.dataframe(
281
- lb[["rank", "Human (ceiling) %", "Heuristic (floor) %",
282
- "human-scored %", "n"]],
283
- use_container_width=True)
284
- st.download_button("Download leaderboard (CSV)",
285
- lb.to_csv().encode(), "human_leaderboard.csv")
286
-
287
- with tab_diff:
288
- st.subheader(f"How hard is each culture? (N={len(complete)}/"
289
- f"{TOTAL_COMMUNITIES} complete communities)")
290
- d = community_difficulty(m, complete, strict)
291
- st.plotly_chart(difficulty_fig(d), use_container_width=True)
292
- hardest = ", ".join(d.head(3).index)
293
- easiest = ", ".join(d.tail(3).index)
294
- st.caption(f"Mean accuracy across all {m['model_display'].nunique()} models "
295
- f"per community. Hardest: {hardest}. Easiest: {easiest}. "
296
- "Grey = heuristic floor, blue = human ceiling.")
297
- st.dataframe(d, use_container_width=True)
298
- st.download_button("Download community difficulty (CSV)",
299
- d.to_csv().encode(), "community_difficulty.csv")
300
-
301
- with tab_kappa:
302
- st.subheader("Does the auto-judge agree with humans?")
303
- per, overall, misses, overcredit, n = community_kappa(m, complete)
304
- k1, k2, k3 = st.columns(3)
305
- k1.metric("Overall Cohen's κ", f"{overall:.3f}",
306
- help="Agreement between heuristic judge and human, on human-scored rows.")
307
- k2.metric("Human-correct the judge missed", f"{misses:,}",
308
- help="Answers humans scored correct but strict string-matching rejected.")
309
- k3.metric("Judge over-credits", f"{overcredit:,}",
310
- help="Answers the judge marked correct but humans rejected.")
311
- st.plotly_chart(hbar(per, "Cohen's κ (judge vs human)", CEIL_COLOR, ".3f"),
312
- use_container_width=True)
313
- st.caption(
314
- f"On {n:,} human-scored rows in complete communities. Low κ flags "
315
- "communities where strict string-matching is unreliable (typically "
316
- "non-Latin scripts and rich morphology). The judge misses far more "
317
- f"correct answers ({misses:,}) than it over-credits ({overcredit:,}), "
318
- "which is why the human ceiling sits well above the heuristic floor.")
319
- st.download_button("Download per-community κ (CSV)",
320
- per.to_csv().encode(), "community_kappa.csv")
321
-
322
- with tab_map:
323
- st.subheader("Coverage map")
324
- map_img = DATA / "coverage_map.png"
325
- if map_img.exists():
326
- st.image(str(map_img), use_container_width=True)
327
- st.caption("Coverage map — visualization by Pouya Sadeghi (Cohere Labs). "
328
- "Drop an updated image at data/coverage_map.png to replace.")
329
- else:
330
- st.info(
331
- "No map image yet. Add Pouya Sadeghi's coverage map as "
332
- "`data/coverage_map.png` and it will render here, credited. "
333
- "We can later augment it by shading each community by human "
334
- "accuracy from the difficulty tab.")
335
-
336
- with tab_cov:
337
- st.subheader("Human-eval coverage by community")
338
- order = {"complete": 0, "partial": 1, "thin": 2, "not_started": 3}
339
- view = cov.reset_index().rename(columns={"index": "community"})
340
- view["_o"] = view["status"].map(order)
341
- view = view.sort_values(["_o", "pct_complete"], ascending=[True, False])
342
- st.dataframe(
343
- view[["community", "status", "pct_complete", "human_done",
344
- "pending", "human_eligible"]],
345
- use_container_width=True, hide_index=True)
346
- chase = view[view.status.isin(["not_started", "thin"])]["community"].tolist()
347
- if chase:
348
- st.info("Chase list (not started / thin): " + ", ".join(chase))
349
-
350
- st.divider()
351
- st.caption(
352
- "Methodology: single human annotator per riddle (no inter-annotator "
353
- "agreement). Leaderboard restricted to communities with complete human "
354
- "coverage; other communities excluded until done. Source of truth: "
355
- "model_riddle_results_flat.csv (Henry). Cuba riddle "
356
- "spanish_cuba_history_016 excluded (redone in v2).")