Spaces:
Runtime error
Runtime error
VityaVitalich Claude Opus 4.7 (1M context) commited on
Commit ·
0567eb0
1
Parent(s): 6a7aa48
dashboard: tabs layout + extraction-generations view
Browse filesLayout: per-question cards now use tabs instead of side-by-side
columns, so each answer renders full-width and reads naturally.
Applies to baseline-vs-steered cards and to the
extraction-method-comparison view.
New view: "extraction generations (pos vs neg)" — walks
data/model_responses/extract/<model>/<rev>/<vec>_{pos,neg}_instruct.csv
and shows the persona+ vs persona- generations that defined each
vector, sortable by pos-neg trait gap. Includes the 4 evil
extraction-method pairs on Olmo-3-1025-7B/main.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- analysis/dashboard.py +230 -39
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_character_neutral_q_neg_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_character_neutral_q_pos_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_combined_neg_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_combined_pos_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_dialogue_neutral_q_neg_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_dialogue_neutral_q_pos_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_stories_neutral_q_neg_instruct.csv +0 -0
- data/model_responses/extract/Olmo-3-1025-7B/main/evil_stories_neutral_q_pos_instruct.csv +0 -0
analysis/dashboard.py
CHANGED
|
@@ -28,6 +28,7 @@ import streamlit as st
|
|
| 28 |
|
| 29 |
REPO = Path(__file__).resolve().parents[1]
|
| 30 |
EVAL_ROOT = REPO / "data/model_responses/eval"
|
|
|
|
| 31 |
|
| 32 |
# Known persona-vector trait prefixes. Judge-score columns inside CSVs
|
| 33 |
# take the form "<prefix>_character_neutral_q", "<prefix>_stories_neutral_q",
|
|
@@ -313,6 +314,64 @@ def load_csv(path: Path) -> pd.DataFrame:
|
|
| 313 |
return pd.read_csv(path)
|
| 314 |
|
| 315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
def _safe_mean(s: pd.Series) -> float:
|
| 317 |
return float(s.dropna().mean()) if len(s.dropna()) else float("nan")
|
| 318 |
|
|
@@ -324,54 +383,70 @@ def _sparse_tail(path: Path | None) -> str:
|
|
| 324 |
return "…/" + "/".join(parts[-5:]) if len(parts) >= 5 else str(path)
|
| 325 |
|
| 326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
def _render_pair(question: str, base_row: pd.Series | None, steer_row: pd.Series,
|
| 328 |
trait_col: str) -> None:
|
|
|
|
| 329 |
st.markdown(f"**Q:** {question}")
|
| 330 |
-
c1, c2 = st.columns(2)
|
| 331 |
bt = base_row.get(trait_col, float("nan")) if base_row is not None else float("nan")
|
| 332 |
bc = base_row.get("coherence", float("nan")) if base_row is not None else float("nan")
|
| 333 |
st_t = steer_row.get(trait_col, float("nan"))
|
| 334 |
st_c = steer_row.get("coherence", float("nan"))
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
if base_row is None:
|
| 338 |
st.info("no baseline row for this question_id")
|
| 339 |
else:
|
| 340 |
-
st.
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
dt = (st_t - bt) if pd.notna(bt) else float("nan")
|
| 344 |
-
st.markdown("*steered*")
|
| 345 |
-
dt_str = f" (Δ={dt:+.1f})" if pd.notna(dt) else ""
|
| 346 |
-
st.caption(f"trait={st_t:.1f}{dt_str} coh={st_c:.1f}")
|
| 347 |
-
st.write(str(steer_row.get("answer", "")).strip() or "_(empty)_")
|
| 348 |
st.divider()
|
| 349 |
|
| 350 |
|
| 351 |
def _render_compare(question: str, base_row: pd.Series | None,
|
| 352 |
method_runs: list[tuple[str, pd.Series]],
|
| 353 |
trait_col: str) -> None:
|
| 354 |
-
"""
|
| 355 |
st.markdown(f"**Q:** {question}")
|
| 356 |
-
columns = st.columns(1 + len(method_runs))
|
| 357 |
bt = base_row.get(trait_col, float("nan")) if base_row is not None else float("nan")
|
| 358 |
bc = base_row.get("coherence", float("nan")) if base_row is not None else float("nan")
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
if base_row is None:
|
| 362 |
st.info("no baseline row")
|
| 363 |
else:
|
| 364 |
-
st.
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
st_t = s_row.get(trait_col, float("nan"))
|
| 369 |
-
st_c = s_row.get("coherence", float("nan"))
|
| 370 |
-
dt = (st_t - bt) if pd.notna(bt) else float("nan")
|
| 371 |
-
dt_str = f" (Δ={dt:+.1f})" if pd.notna(dt) else ""
|
| 372 |
-
st.markdown(f"*{method}*")
|
| 373 |
-
st.caption(f"trait={st_t:.1f}{dt_str} coh={st_c:.1f}")
|
| 374 |
-
st.write(str(s_row.get("answer", "")).strip() or "_(empty)_")
|
| 375 |
st.divider()
|
| 376 |
|
| 377 |
|
|
@@ -649,34 +724,146 @@ def _render_compare_view(runs_c: list[Run]) -> None:
|
|
| 649 |
_render_compare(question, b_row, method_rows, trait_col)
|
| 650 |
|
| 651 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
def main() -> None:
|
| 653 |
st.set_page_config(page_title="Steered-output explorer", layout="wide")
|
| 654 |
st.title("Steered-output explorer")
|
| 655 |
|
| 656 |
runs = discover()
|
| 657 |
-
|
| 658 |
-
|
|
|
|
| 659 |
return
|
| 660 |
|
| 661 |
st.sidebar.header("view")
|
| 662 |
-
view_options: list[str] = [
|
|
|
|
|
|
|
| 663 |
if any(r.run_kind == "ablation" for r in runs):
|
| 664 |
view_options.append("vector extraction ablation")
|
|
|
|
|
|
|
| 665 |
view = st.sidebar.radio("data view", view_options, index=0,
|
| 666 |
label_visibility="collapsed")
|
| 667 |
-
view_kind = "ablation" if view.startswith("vector extraction") else "sweep"
|
| 668 |
-
|
| 669 |
-
runs_v = [r for r in runs if r.run_kind == view_kind]
|
| 670 |
-
if not runs_v:
|
| 671 |
-
st.warning(f"No runs available for view '{view}'.")
|
| 672 |
-
return
|
| 673 |
|
| 674 |
-
if
|
|
|
|
| 675 |
run = _sweep_sidebar(runs_v)
|
| 676 |
if run is None:
|
| 677 |
return
|
| 678 |
_render_single(run)
|
| 679 |
-
|
|
|
|
| 680 |
runs_c, compare = _ablation_sidebar(runs_v)
|
| 681 |
if not runs_c:
|
| 682 |
st.warning("No run matches the current filters.")
|
|
@@ -684,14 +871,18 @@ def main() -> None:
|
|
| 684 |
if compare and len({r.extract_rev for r in runs_c}) > 1:
|
| 685 |
_render_compare_view(runs_c)
|
| 686 |
else:
|
| 687 |
-
# single method (or only one method available) — fall back to the
|
| 688 |
-
# single-run view, picking a deterministic representative
|
| 689 |
if len(runs_c) > 1:
|
| 690 |
run = st.sidebar.selectbox("run file (disambiguate)", runs_c,
|
| 691 |
format_func=lambda r: _sparse_tail(r.steered_path))
|
| 692 |
else:
|
| 693 |
run = runs_c[0]
|
| 694 |
_render_single(run)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
|
| 696 |
|
| 697 |
if __name__ == "__main__":
|
|
|
|
| 28 |
|
| 29 |
REPO = Path(__file__).resolve().parents[1]
|
| 30 |
EVAL_ROOT = REPO / "data/model_responses/eval"
|
| 31 |
+
EXTRACT_ROOT = REPO / "data/model_responses/extract"
|
| 32 |
|
| 33 |
# Known persona-vector trait prefixes. Judge-score columns inside CSVs
|
| 34 |
# take the form "<prefix>_character_neutral_q", "<prefix>_stories_neutral_q",
|
|
|
|
| 314 |
return pd.read_csv(path)
|
| 315 |
|
| 316 |
|
| 317 |
+
@dataclass(frozen=True)
|
| 318 |
+
class Extract:
|
| 319 |
+
"""One extraction-time pair (positive- and negative-persona generations
|
| 320 |
+
that defined a persona vector)."""
|
| 321 |
+
extract_model: str
|
| 322 |
+
extract_rev: str
|
| 323 |
+
trait: str
|
| 324 |
+
vec: str # extraction method id, e.g. evil_character_neutral_q
|
| 325 |
+
pos_path: Path
|
| 326 |
+
neg_path: Path
|
| 327 |
+
trait_col: str
|
| 328 |
+
|
| 329 |
+
def title(self) -> str:
|
| 330 |
+
return (f"extraction method: **{self.vec}** · "
|
| 331 |
+
f"**{self.extract_model}** @ **{self.extract_rev}**")
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
@st.cache_data(show_spinner="Discovering extraction CSVs…")
|
| 335 |
+
def discover_extracts() -> list[Extract]:
|
| 336 |
+
"""Walk EXTRACT_ROOT for paired `<vec>_pos_instruct.csv` /
|
| 337 |
+
`<vec>_neg_instruct.csv` files and keep only those whose CSV header
|
| 338 |
+
contains a recognizable trait-score column."""
|
| 339 |
+
out: list[Extract] = []
|
| 340 |
+
if not EXTRACT_ROOT.exists():
|
| 341 |
+
return out
|
| 342 |
+
for model_dir in sorted(EXTRACT_ROOT.iterdir()):
|
| 343 |
+
if not model_dir.is_dir():
|
| 344 |
+
continue
|
| 345 |
+
for rev_dir in sorted(model_dir.iterdir()):
|
| 346 |
+
if not rev_dir.is_dir():
|
| 347 |
+
continue
|
| 348 |
+
pos_files = {p.name[:-len("_pos_instruct.csv")]: p
|
| 349 |
+
for p in rev_dir.glob("*_pos_instruct.csv")}
|
| 350 |
+
for vec, pos_path in pos_files.items():
|
| 351 |
+
neg_path = rev_dir / f"{vec}_neg_instruct.csv"
|
| 352 |
+
if not neg_path.exists():
|
| 353 |
+
continue
|
| 354 |
+
cols = _read_header(pos_path)
|
| 355 |
+
if cols is None:
|
| 356 |
+
continue
|
| 357 |
+
tc = find_trait_column(cols)
|
| 358 |
+
if tc is None:
|
| 359 |
+
continue
|
| 360 |
+
trait_col, trait = tc
|
| 361 |
+
if trait_col not in (_read_header(neg_path) or []):
|
| 362 |
+
continue
|
| 363 |
+
out.append(Extract(
|
| 364 |
+
extract_model=model_dir.name,
|
| 365 |
+
extract_rev=rev_dir.name,
|
| 366 |
+
trait=trait,
|
| 367 |
+
vec=vec,
|
| 368 |
+
pos_path=pos_path,
|
| 369 |
+
neg_path=neg_path,
|
| 370 |
+
trait_col=trait_col,
|
| 371 |
+
))
|
| 372 |
+
return out
|
| 373 |
+
|
| 374 |
+
|
| 375 |
def _safe_mean(s: pd.Series) -> float:
|
| 376 |
return float(s.dropna().mean()) if len(s.dropna()) else float("nan")
|
| 377 |
|
|
|
|
| 383 |
return "…/" + "/".join(parts[-5:]) if len(parts) >= 5 else str(path)
|
| 384 |
|
| 385 |
|
| 386 |
+
def _answer_text(row: pd.Series | None) -> str:
|
| 387 |
+
if row is None:
|
| 388 |
+
return "_(no row)_"
|
| 389 |
+
txt = str(row.get("answer", "")).strip()
|
| 390 |
+
return txt or "_(empty)_"
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def _fmt_num(x) -> str:
|
| 394 |
+
return f"{x:.1f}" if pd.notna(x) else "—"
|
| 395 |
+
|
| 396 |
+
|
| 397 |
def _render_pair(question: str, base_row: pd.Series | None, steer_row: pd.Series,
|
| 398 |
trait_col: str) -> None:
|
| 399 |
+
"""Per-question card: tabs for baseline / steered, full-width text."""
|
| 400 |
st.markdown(f"**Q:** {question}")
|
|
|
|
| 401 |
bt = base_row.get(trait_col, float("nan")) if base_row is not None else float("nan")
|
| 402 |
bc = base_row.get("coherence", float("nan")) if base_row is not None else float("nan")
|
| 403 |
st_t = steer_row.get(trait_col, float("nan"))
|
| 404 |
st_c = steer_row.get("coherence", float("nan"))
|
| 405 |
+
dt = (st_t - bt) if pd.notna(bt) else float("nan")
|
| 406 |
+
dt_str = f" Δ={dt:+.1f}" if pd.notna(dt) else ""
|
| 407 |
+
|
| 408 |
+
base_label = f"baseline trait={_fmt_num(bt)} coh={_fmt_num(bc)}"
|
| 409 |
+
steer_label = f"steered trait={_fmt_num(st_t)}{dt_str} coh={_fmt_num(st_c)}"
|
| 410 |
+
tab_b, tab_s = st.tabs([base_label, steer_label])
|
| 411 |
+
with tab_b:
|
| 412 |
if base_row is None:
|
| 413 |
st.info("no baseline row for this question_id")
|
| 414 |
else:
|
| 415 |
+
st.write(_answer_text(base_row))
|
| 416 |
+
with tab_s:
|
| 417 |
+
st.write(_answer_text(steer_row))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
st.divider()
|
| 419 |
|
| 420 |
|
| 421 |
def _render_compare(question: str, base_row: pd.Series | None,
|
| 422 |
method_runs: list[tuple[str, pd.Series]],
|
| 423 |
trait_col: str) -> None:
|
| 424 |
+
"""Tabs across baseline + each extraction method; full-width answer text."""
|
| 425 |
st.markdown(f"**Q:** {question}")
|
|
|
|
| 426 |
bt = base_row.get(trait_col, float("nan")) if base_row is not None else float("nan")
|
| 427 |
bc = base_row.get("coherence", float("nan")) if base_row is not None else float("nan")
|
| 428 |
+
|
| 429 |
+
tab_labels: list[str] = [
|
| 430 |
+
f"baseline trait={_fmt_num(bt)} coh={_fmt_num(bc)}"
|
| 431 |
+
]
|
| 432 |
+
for method, s_row in method_runs:
|
| 433 |
+
st_t = s_row.get(trait_col, float("nan"))
|
| 434 |
+
st_c = s_row.get("coherence", float("nan"))
|
| 435 |
+
dt = (st_t - bt) if pd.notna(bt) else float("nan")
|
| 436 |
+
dt_str = f" Δ={dt:+.1f}" if pd.notna(dt) else ""
|
| 437 |
+
tab_labels.append(
|
| 438 |
+
f"{method} trait={_fmt_num(st_t)}{dt_str} coh={_fmt_num(st_c)}"
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
tabs = st.tabs(tab_labels)
|
| 442 |
+
with tabs[0]:
|
| 443 |
if base_row is None:
|
| 444 |
st.info("no baseline row")
|
| 445 |
else:
|
| 446 |
+
st.write(_answer_text(base_row))
|
| 447 |
+
for tab, (_method, s_row) in zip(tabs[1:], method_runs):
|
| 448 |
+
with tab:
|
| 449 |
+
st.write(_answer_text(s_row))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
st.divider()
|
| 451 |
|
| 452 |
|
|
|
|
| 724 |
_render_compare(question, b_row, method_rows, trait_col)
|
| 725 |
|
| 726 |
|
| 727 |
+
def _extraction_sidebar(extracts: list[Extract]) -> Extract | None:
|
| 728 |
+
st.sidebar.header("model")
|
| 729 |
+
models = sorted({e.extract_model for e in extracts})
|
| 730 |
+
default_idx = (models.index("Olmo-3-1025-7B")
|
| 731 |
+
if "Olmo-3-1025-7B" in models else 0)
|
| 732 |
+
model = st.sidebar.selectbox("model", models, index=default_idx)
|
| 733 |
+
e_m = [e for e in extracts if e.extract_model == model]
|
| 734 |
+
|
| 735 |
+
revs = sorted({e.extract_rev for e in e_m}, key=_rev_sort_key)
|
| 736 |
+
if len(revs) > 1:
|
| 737 |
+
rev = st.sidebar.selectbox("checkpoint", revs, index=len(revs) - 1)
|
| 738 |
+
else:
|
| 739 |
+
rev = revs[0]
|
| 740 |
+
st.sidebar.caption(f"checkpoint **{rev}** (only option)")
|
| 741 |
+
e_r = [e for e in e_m if e.extract_rev == rev]
|
| 742 |
+
|
| 743 |
+
st.sidebar.header("persona vector")
|
| 744 |
+
traits = sorted({e.trait for e in e_r})
|
| 745 |
+
trait = st.sidebar.selectbox("trait", traits)
|
| 746 |
+
e_t = [e for e in e_r if e.trait == trait]
|
| 747 |
+
|
| 748 |
+
methods = sorted({e.vec for e in e_t})
|
| 749 |
+
if len(methods) > 1:
|
| 750 |
+
method = st.sidebar.selectbox("extraction method", methods)
|
| 751 |
+
else:
|
| 752 |
+
method = methods[0]
|
| 753 |
+
st.sidebar.caption(f"extraction method **{method}** (only option)")
|
| 754 |
+
matches = [e for e in e_t if e.vec == method]
|
| 755 |
+
return matches[0] if matches else None
|
| 756 |
+
|
| 757 |
+
|
| 758 |
+
def _render_extraction_view(extract: Extract) -> None:
|
| 759 |
+
st.markdown(extract.title())
|
| 760 |
+
st.caption(f"pos: `{_sparse_tail(extract.pos_path)}`"
|
| 761 |
+
f" · neg: `{_sparse_tail(extract.neg_path)}`")
|
| 762 |
+
|
| 763 |
+
pos_df = load_csv(extract.pos_path)
|
| 764 |
+
neg_df = load_csv(extract.neg_path)
|
| 765 |
+
trait_col = extract.trait_col
|
| 766 |
+
|
| 767 |
+
pos_mean = _safe_mean(pos_df[trait_col])
|
| 768 |
+
neg_mean = _safe_mean(neg_df[trait_col])
|
| 769 |
+
pos_coh = _safe_mean(pos_df["coherence"]) if "coherence" in pos_df else float("nan")
|
| 770 |
+
neg_coh = _safe_mean(neg_df["coherence"]) if "coherence" in neg_df else float("nan")
|
| 771 |
+
cols = st.columns(4)
|
| 772 |
+
cols[0].metric("pos persona trait", f"{pos_mean:.2f}")
|
| 773 |
+
cols[1].metric("neg persona trait", f"{neg_mean:.2f}",
|
| 774 |
+
delta=f"{neg_mean - pos_mean:+.2f}")
|
| 775 |
+
cols[2].metric("pos persona coherence", f"{pos_coh:.2f}")
|
| 776 |
+
cols[3].metric("neg persona coherence", f"{neg_coh:.2f}")
|
| 777 |
+
|
| 778 |
+
order = st.radio("sort per-question cards by",
|
| 779 |
+
("pos − neg trait gap (most-polarizing first)",
|
| 780 |
+
"pos persona trait (descending)",
|
| 781 |
+
"neg persona trait (descending)",
|
| 782 |
+
"question text"),
|
| 783 |
+
horizontal=True)
|
| 784 |
+
|
| 785 |
+
# group all generations (pos & neg) by question text
|
| 786 |
+
questions = sorted(set(pos_df["question"]).union(neg_df["question"]))
|
| 787 |
+
cards: list[tuple[str, pd.DataFrame, pd.DataFrame, float, float]] = []
|
| 788 |
+
for q in questions:
|
| 789 |
+
p = pos_df[pos_df["question"] == q]
|
| 790 |
+
n = neg_df[neg_df["question"] == q]
|
| 791 |
+
pm = _safe_mean(p[trait_col]) if len(p) else float("nan")
|
| 792 |
+
nm = _safe_mean(n[trait_col]) if len(n) else float("nan")
|
| 793 |
+
cards.append((q, p, n, pm, nm))
|
| 794 |
+
|
| 795 |
+
def _gap(c):
|
| 796 |
+
_, _, _, pm, nm = c
|
| 797 |
+
if pd.isna(pm) or pd.isna(nm):
|
| 798 |
+
return 0.0
|
| 799 |
+
return float(pm - nm)
|
| 800 |
+
|
| 801 |
+
if order.startswith("pos − neg"):
|
| 802 |
+
cards.sort(key=lambda c: -_gap(c))
|
| 803 |
+
elif order.startswith("pos persona"):
|
| 804 |
+
cards.sort(key=lambda c: -(c[3] if pd.notna(c[3]) else float("-inf")))
|
| 805 |
+
elif order.startswith("neg persona"):
|
| 806 |
+
cards.sort(key=lambda c: -(c[4] if pd.notna(c[4]) else float("-inf")))
|
| 807 |
+
else:
|
| 808 |
+
cards.sort(key=lambda c: c[0])
|
| 809 |
+
|
| 810 |
+
top_k = st.slider("show top-N questions", 5, len(cards),
|
| 811 |
+
min(20, len(cards)))
|
| 812 |
+
for question, p, n, pm, nm in cards[:top_k]:
|
| 813 |
+
st.markdown(f"**Q:** {question}")
|
| 814 |
+
pos_label = f"pos persona trait={_fmt_num(pm)} ({len(p)} gens)"
|
| 815 |
+
neg_label = f"neg persona trait={_fmt_num(nm)} ({len(n)} gens)"
|
| 816 |
+
tab_p, tab_n = st.tabs([pos_label, neg_label])
|
| 817 |
+
with tab_p:
|
| 818 |
+
_render_gens(p, trait_col)
|
| 819 |
+
with tab_n:
|
| 820 |
+
_render_gens(n, trait_col)
|
| 821 |
+
st.divider()
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
def _render_gens(df: pd.DataFrame, trait_col: str) -> None:
|
| 825 |
+
if df.empty:
|
| 826 |
+
st.info("no generations for this question on this side")
|
| 827 |
+
return
|
| 828 |
+
df = df.sort_values(trait_col, ascending=False, na_position="last")
|
| 829 |
+
for i, (_, row) in enumerate(df.iterrows(), start=1):
|
| 830 |
+
t = row.get(trait_col, float("nan"))
|
| 831 |
+
c = row.get("coherence", float("nan"))
|
| 832 |
+
st.caption(f"gen {i} trait={_fmt_num(t)} coh={_fmt_num(c)}")
|
| 833 |
+
st.write(_answer_text(row))
|
| 834 |
+
if i < len(df):
|
| 835 |
+
st.markdown("---")
|
| 836 |
+
|
| 837 |
+
|
| 838 |
def main() -> None:
|
| 839 |
st.set_page_config(page_title="Steered-output explorer", layout="wide")
|
| 840 |
st.title("Steered-output explorer")
|
| 841 |
|
| 842 |
runs = discover()
|
| 843 |
+
extracts = discover_extracts()
|
| 844 |
+
if not runs and not extracts:
|
| 845 |
+
st.error(f"No CSVs found under {EVAL_ROOT} or {EXTRACT_ROOT}")
|
| 846 |
return
|
| 847 |
|
| 848 |
st.sidebar.header("view")
|
| 849 |
+
view_options: list[str] = []
|
| 850 |
+
if runs:
|
| 851 |
+
view_options.append("checkpoint sweep")
|
| 852 |
if any(r.run_kind == "ablation" for r in runs):
|
| 853 |
view_options.append("vector extraction ablation")
|
| 854 |
+
if extracts:
|
| 855 |
+
view_options.append("extraction generations (pos vs neg)")
|
| 856 |
view = st.sidebar.radio("data view", view_options, index=0,
|
| 857 |
label_visibility="collapsed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
|
| 859 |
+
if view == "checkpoint sweep":
|
| 860 |
+
runs_v = [r for r in runs if r.run_kind == "sweep"]
|
| 861 |
run = _sweep_sidebar(runs_v)
|
| 862 |
if run is None:
|
| 863 |
return
|
| 864 |
_render_single(run)
|
| 865 |
+
elif view == "vector extraction ablation":
|
| 866 |
+
runs_v = [r for r in runs if r.run_kind == "ablation"]
|
| 867 |
runs_c, compare = _ablation_sidebar(runs_v)
|
| 868 |
if not runs_c:
|
| 869 |
st.warning("No run matches the current filters.")
|
|
|
|
| 871 |
if compare and len({r.extract_rev for r in runs_c}) > 1:
|
| 872 |
_render_compare_view(runs_c)
|
| 873 |
else:
|
|
|
|
|
|
|
| 874 |
if len(runs_c) > 1:
|
| 875 |
run = st.sidebar.selectbox("run file (disambiguate)", runs_c,
|
| 876 |
format_func=lambda r: _sparse_tail(r.steered_path))
|
| 877 |
else:
|
| 878 |
run = runs_c[0]
|
| 879 |
_render_single(run)
|
| 880 |
+
else: # extraction generations
|
| 881 |
+
ext = _extraction_sidebar(extracts)
|
| 882 |
+
if ext is None:
|
| 883 |
+
st.warning("No extraction pair matches the current filters.")
|
| 884 |
+
return
|
| 885 |
+
_render_extraction_view(ext)
|
| 886 |
|
| 887 |
|
| 888 |
if __name__ == "__main__":
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_character_neutral_q_neg_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_character_neutral_q_pos_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_combined_neg_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_combined_pos_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_dialogue_neutral_q_neg_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_dialogue_neutral_q_pos_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_stories_neutral_q_neg_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_responses/extract/Olmo-3-1025-7B/main/evil_stories_neutral_q_pos_instruct.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|