Reviewer 2×2 analysis: aggregation, refusal-rate table, and topic specificity scoring - #8
Open
canrager wants to merge 13 commits into
Open
Reviewer 2×2 analysis: aggregation, refusal-rate table, and topic specificity scoring#8canrager wants to merge 13 commits into
canrager wants to merge 13 commits into
Conversation
Persist per-probe cascade verdicts and refusal rate on Topic, and add a Hydra entrypoint that drives check_refusal over the cluster heads in an aggregation output directory. Emits reviewer-table-shaped artifacts (metadata / per_cluster / per_topic) matching reviewer_refusal_probe.json, with discovery booleans per 2x2 cell read from reduction_log.json's source_sets. Adds runpod_control.py --task dispatch so the same controller can launch either the 2x2 ablation or the new refusal-rate driver under tmux. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Post-Hoc Refusal-Rate Table subsection under Runpod Reviewer-Ready Runs covering aggregation, the refusal_rates --task driver, and the fetched artifact bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a follow-on smoke recipe right after the 2x2 smoke command: fetch candidate JSONs, backfill head_refusal_topics_summaries from head_topics (since the 2x2 default leaves the field empty), run aggregation locally with small batches, scp the aggregation dir to the pod, and start a refusal-rate run with a low probe count. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Aggregator: when head_refusal_topics_summaries is empty (discovery-mode crawls), fall back to queue.topics.head_topics[].summary so no backfill step is needed. Refusal-filtered crawls keep their existing behavior. - New scripts/runpod_run_aggregation.sh: env-driven pod-side driver. With REVIEWER_OUT_DIR set, auto-discovers one crawler_out_*_<cell>.json per 2x2 cell, runs the aggregator, and writes artifacts/out/runpod_latest_aggregation.txt. - runpod_control.py: adds the aggregation task. start --task aggregation defaults --reviewer-out-dir from the latest reviewer_ablation marker; start --task refusal_rates defaults --aggregation-dir from the latest aggregation marker. The three-stage pipeline now runs entirely on the pod with no intermediate fetch/upload. - README: rewrites the Post-Hoc Refusal-Rate Table and the smoke section to use the marker-chained pod commands, drops the jq backfill step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Guard `from vllm import ...` behind try/except in generation_utils and llm_utils so the package installs and runs on hosts without a vLLM wheel (e.g. macOS). The vLLM code paths now raise only if actually invoked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps handoff/bundle tarballs out of the repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… model to gemini-3.1-flash The experiments Hydra group was always aggregation configuration. Rename it to a dedicated `aggregation` group for clarity: - configs/experiments/ -> configs/aggregation/ (default + olmo3_*/qwen_* sweeps) - ExperimentsConfig -> AggregationConfig; CrawlerConfig.experiments -> .aggregation - run_aggregation.py / run_coverage.py / aggregator.py read config.aggregation - run_aggregation.sh / run_coverage.sh / runpod_run_aggregation.sh use `aggregation=<name>` and `aggregation.*` overrides - README config-groups, runpod, and Aggregation sections updated Also switch the default aggregation_model from moonshotai/kimi-k2-0905 to google/gemini-3.1-flash. kimi-k2 repeatedly truncated/timed out (90s hard cap) on the long reduction JSON, dumping clusters unreduced; gemini-3.1-flash is faster and reliable on the same batches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
google/gemini-3.1-flash is not a valid OpenRouter model id (400). Switch
all aggregation presets to google/gemini-3.5-flash. Also raise max_tokens
4096 -> 16384: the long reduction-JSON outputs were truncated mid-object
('Unterminated JSON object'), causing parse failures, exhausted retries,
and silent pass-through of unmerged topics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a 'fixed taxonomy' mode to the aggregation pipeline: when
aggregation.fixed_topics_path is set, run_aggregation classifies every
input topic into a predefined list (one per line) via a single LLM pass
instead of discovering clusters through iterative reduction.
- TopicAggregator.classify(): multi-label assignment of each input to one
or more fixed topics, with an 'Unmatched' bucket for non-fitting topics;
outputs are constrained to the fixed set (hallucinated keys -> Unmatched)
and values filtered to actual batch inputs. Reuses the existing coverage
retry, source propagation, reduction-log, and artifact machinery, so it
emits the same final_topics.txt / reduction_log.json / explorer.html.
- save_cell_matrix(): per-topic x per-input-cell contribution matrix
(presence + counts) as topic_cell_matrix.{csv,md}.
- configs/aggregation/fixed_q8p4.yaml: preset for the q8p4 2x2 reviewer run.
Note: the preset points fixed_topics_path at an artifacts/ path that is
gitignored; supply your own taxonomy file when reusing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st-occurrence cols and discovery-curve plot load_topics now recovers each topic's raw discovery id (via the source crawl referenced in q8p4_candidate_export, joined by english/shortened label) and the run's num_total_topics, returning topic_ids and run_totals. save_cell_matrix emits per-cell first_abs/first_rel columns (earliest discovery id in the cluster, absolute and relative to the run total). New save_cluster_discovery_plot draws a cumulative step curve of clusters discovered vs topic id, one line per cell, auto-saved alongside the matrix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add aggregation.input_groups (cell_name -> [paths]) so replicate runs of a
condition aggregate into a single matrix column. load_topics now takes
ordered (cell, [paths]) groups and returns topic_first (norm topic ->
{cell: (abs_id, total)}), picking the earliest relative occurrence across a
cell's pooled files; save_cell_matrix / save_cluster_discovery_plot consume
cell_names + topic_first. Flat input_paths still maps one file per cell
(output byte-identical to before).
Also fix the discovery-mode fallback to label head topics by
english/shortened when summary is unset, so raw do_filter_refusals=false
crawls aggregate. Add configs/aggregation/budget5.yaml and document
input_groups in the README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Score each candidate topic by how specific it is on an L1-L5 ladder (+ Junk)
instead of only clustering into a taxonomy, so crawling methods can be compared
on how many *specific* (L4/L5) instances they surface rather than just broad
category coverage.
Scoring:
- SPECIFICITY_PROMPT (ladder + decision cascade + tie-break) and score_specificity
config fields; reuses the classification machinery via a new _classify_into()
helper (classify() is now a thin caller — behavior-preserving).
- score_specificity() forces a single most-specific level per topic; emits
specificity_scores.csv, specificity_by_cell.{csv,md,png}, and a grouped bar.
Explorer:
- build_specificity_explorer_html(): a unified per-topic explorer where every
topic carries method, specificity, and cluster. Switch grouping between the
three dimensions; pin Method/Specificity/Cluster via dropdown filters; search;
legends, overall + per-group summaries; each topic row labeled with all three
regardless of grouping.
- Cluster labels come from also classifying into the fixed taxonomy when
fixed_topics_path is set; save_artifacts gains write_explorer to skip the
unused reduction-tree explorer in specificity mode.
Adds configs/aggregation/budget5_specificity.yaml, tests/test_specificity.py,
and a root-anchored .gitignore rule for local reviewer_ablation_* run data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a specificity-level mode to the post-hoc refusal-rate stage so a specificity-scoring aggregation run (whose final_topics.txt holds only the level labels) can be probed for the topics at one or more levels. - run_refusal_rates.py: +specificity_level=L5 (or [L4,L5]) reads cluster heads and per-cell discovery from specificity_scores.csv instead of final_topics.txt + reduction_log.json; rows carry specificity_level and the markdown gains a Level column. Default final_topics path is unchanged. - runpod_compute_refusal_rates.sh: SPECIFICITY_LEVEL/SPECIFICITY_CSV env vars; multi-level passed as a Hydra list to avoid sweep parsing; csv-aware guard. - runpod_control.py: --level / --specificity-csv on `start --task refusal_rates`. - Tests for the csv selector (single + multi-level union) and level parsing. - README: document the --level flow and the Level column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Turns the reviewer 2×2 ablation (iteration × prefill, run with
crawler.do_filter_refusals=false) into analysis, all on top of one sharedaggregation pipeline. Three related deliverables:
Each is independently useful; together they answer "do the stronger crawl methods surface more, and more specific, genuinely-refused topics?"
1. Post-hoc refusal-rate table
The 2×2 cells surface candidate topics, not behavior-confirmed refusals. This pass re-probes the aggregated cluster heads against the target and produces one table per cluster: refusal rate plus discovery booleans for each of the four cells. The whole follow-on runs on the pod with no intermediate local fetch; each stage drops a
runpod_latest_*marker the next stage auto-consumes.Outputs (
scripts/runpod_compute_refusal_rates.sh→src/run_refusal_rates.py) →artifacts/refusal_rates/<ts>/:refusal_rates.md— reviewer table sorted by refusal rate desc (cluster, one boolean per cell, refusal rate, refusals / probes).refusal_rates.json—metadata,per_cluster[]withdiscovery.{direct,prefill_only,iter_no_prefill,ipc},per_topic[].config.json,run.log,refusal_rates_*.jsonltranscript; markerartifacts/out/runpod_latest_refusal_rates.txt.Topicpersists per-probe cascade verdicts (refusal_check_refused: List[Optional[bool]]) and aggregaterefusal_rate: Optional[float]; both defaultNone, so older crawler JSONs round-trip unchanged.Driver:
--aggregation-dir/--reviewer-out-dirdefault from the latest pod markers.run_refusal_rates.pyHydra overrides:model=<config>,+aggregation_dir=<path>(required),+out_dir=<path>,crawler.num_refusal_checks_per_topic,crawler.is_refusal_threshold.2. Constrained aggregation + per-cell discovery
src/aggregation/reworked so the 2×2 cells can be aggregated and compared:experiments→aggregation; default agg modelgemini-3.1-flash.fixed_topics_path): classify each input topic into a predefined list instead of discovering clusters via iterative reduction, so cells are comparable against one taxonomy.input_groups: pool several crawl files into one named cell (e.g. 5 replicatedirectcrawls → onedirectcell).topic_cell_matrix.{csv,md}gains per-cellcount_/present_/first_abs_/first_rel_columns, plus acluster_discovery_curve.png(cumulative distinct clusters vs discovery order, one line per cell).Config:
configs/aggregation/{budget5,fixed_q8p4,...}.yaml. Entry:python -m src.aggregation.run_aggregation aggregation=<name>.3. Topic specificity scoring + unified explorer
Category coverage saturates (every method hits the broad buckets), so it can't discriminate methods — the signal is in the long tail of specific instances. This adds a specificity lens and a richer explorer.
Scoring (
score_specificity: true): an LLM judge labels every topic on an L1–L5 ladder (+Junk) — Domain → Theme → Practice → named Case → unique Pinpoint — via a decision cascade + tie-break. Reuses the classification machinery (new_classify_into()helper;classify()is now a thin caller, behavior-preserving). Forces a single most-specific level per topic. Emits:specificity_scores.csv(per-topic level + method presence),specificity_by_cell.{csv,md,png}(level × cell counts/fractions, an L4+L5 specific-tier row),specificity_grouped_bar.png(x-groups = levels, bars = methods).Unified explorer (
build_specificity_explorer_html): every topic carries method, specificity, and cluster. Switch grouping between the three dimensions; pin Method / Specificity / Cluster via dropdown filters (AND-combined with search); legends, overall + per-group summaries; each topic row is labeled with all three regardless of grouping. Cluster labels come from also classifying into the fixed taxonomy whenfixed_topics_pathis set;save_artifactsgainedwrite_explorerto skip the unused reduction-tree explorer in this mode.Config:
configs/aggregation/budget5_specificity.yaml. Headline on budget5 — the L4+L5 specific tier cleanly separates methods (prefill drives specificity even more than iteration).Test plan
Topicround-trips new refusal fields, defaultsNonefor old JSONs.head_topics.runpod_control.pydry-run shows correct env-prefixed driver command per--task.tests/test_specificity.py(4): level bucketing, single-most-specific tie-break, per-cell matrix + plots, explorer records carry method/specificity/cluster, and the_classify_intorefactor leaves the taxonomyclassify()path intact.tests/test_refusal_utils.pypass with the same profile asmain.topic_cell_matrix+ discovery curve).specificity_*tables); explorer verified by joining the specificity + taxonomy runs.start --task aggregation→start --task refusal_rates(--probes-per-topic 3) for marker chaining + schema parity.🤖 Generated with Claude Code