Independent reviewer, multi-model debate, and best-of-N tournament - #316
Conversation
dc90def to
77a560e
Compare
…rnament Reviewing and judging are currently done by the same model that produced the artifact, so the pipeline grades its own homework. This adds three opt-in mechanisms that decouple them, all defaulting to off so existing runs behave exactly as before. Independent reviewer `llm.reviewer_model` (plus optional reviewer_provider / base_url / api_key(_env) for a fully separate provider) builds a reviewer client that never falls back to the author model. Stage 18 peer review and the Stage 20 quality gate use it when configured and fall back to the generator when not. Stage 18 also writes review_provenance.json (author_model, judge_model, independent_reviewer) so a finished run can be audited for reviewer independence after the fact. Multi-model debate (pipeline/debate.py) Roles argue with distinct models, rebut for `debate_rounds`, then an independent judge ranks and a synthesizer writes the final text. Splitting ranking from synthesis matters: when one model both scores and writes, the output collapses into vague consensus. Records debate_record.json. Best-of-N tournament (pipeline/tournament.py) N candidates from diverse stances, an independent judge scores and ranks, the single winner proceeds — so the pipeline stays linear, one canonical artifact per stage. Blank generations are dropped before judging rather than scored (a judge handed an empty candidate will invent a rationale for it). Records tournament_record.json. Stage 8 routes tournament > debate > the existing single-model multi-perspective path, which is untouched and still the default. Both engines depend only on the standard library. Two sub-prompts (tournament_rank, debate_rebuttal) are added; hypothesis_synthesize was already present and is reused. Tests: 2900 passed, 0 failed, 56 skipped (baseline: 2863 passed with one pre-existing flaky failure in test_hitl_advanced, which passed this run). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77a560e to
1d7e46f
Compare
|
Hi @Jiaaqiliu @huaxiuyao — flagging this for whenever you have review bandwidth, no rush. A few notes that may make it cheaper to review:
Also happy to split this into smaller PRs if that's easier to review. The three pieces are separable:
The reviewer client is a dependency of the other two — both use it as the judge — so it would need to go first. |
|
Merged. Large feature PRs usually get held for a slower review; this one landed because the risk surface is genuinely small, and I verified that rather than taking the "all three default to off" claim at face value: Parsed from an empty config. With no config change, Stage 8/18/20 take the same path they did before, and The premise is sound and worth stating plainly: asking the author model whether its own paper passes the quality gate is not a gate. Self-preference in LLM judging is well enough documented that a self-review's silence is not evidence of quality. Three design choices stood out as better than the obvious version:
On the note about Post-merge full suite: 2956 passed, no regressions (baseline before this batch: 2869). The pre-existing |
|
Thanks for the quick turnaround on #316 — and for verifying the "all three default to off" claim instead of taking it on trust. For a PR that size that's the right instinct, and your parse of the actual defaults is a better guarantee than anything I could have asserted in the description. The
On |
Problem
Reviewing and judging are currently performed by the same model that produced the artifact. Stage 18 asks the author model to review the paper it just wrote, and the Stage 20 quality gate asks it whether its own paper is good enough to pass. LLMs show a well-documented preference for their own output, so this makes both stages weaker than they look: a self-review rarely produces the kind of objection that would actually block a run.
What this adds
Three opt-in mechanisms that decouple generation from judgement. All three default to off — with no config changes, every stage behaves exactly as it does today.
1. Independent reviewer
llm.reviewer_modelbuilds a reviewer client that never falls back to the author model. Set only the model to reuse the main provider with a different model, or addreviewer_provider/reviewer_base_url/reviewer_api_key(_env)for a fully separate provider (e.g. generator = GPT, reviewer = Claude).Used by Stage 18 (peer review) and Stage 20 (quality gate); both fall back to the generator when it is unset.
Stage 18 also writes
review_provenance.json—author_model,judge_model,independent_reviewer— plus an HTML comment header inreviews.md, so a finished run can be audited for reviewer independence after the fact rather than taken on trust.2. Multi-model debate (
pipeline/debate.py)Roles argue with distinct models, rebut each other for
debate_rounds, then an independent judge ranks the positions and a synthesizer writes the final text from that ranking.Separating ranking from synthesis is the part that matters. When a single model both scores the positions and writes the summary, the output collapses into vague consensus — the disagreements that make a debate useful get averaged away. Letting the judge only rank (which keeps the anti-self-preference property) and having the stronger model write from that ranking preserves them.
The panel reuses models you already configure:
primary_model+reviewer_model+fallback_models, deduplicated, each cloned into its own single-model client. Recordsdebate_record.json.3. Best-of-N tournament (
pipeline/tournament.py)Generates N candidates from diverse stances, an independent judge scores and ranks them, and the single winner proceeds — the pipeline stays linear, with one canonical artifact per stage.
Blank generations are dropped before judging rather than scored. This is a guard worth having: a judge handed an empty candidate will happily invent a rationale and a score for it, and an empty artifact can then win. Records
tournament_record.json.Stage 8 routes tournament → debate → the existing single-model multi-perspective path. That last path is untouched and remains the default.
Notes for review
tournament_rank,debate_rebuttal).hypothesis_synthesizealready existed and is reused.tournament_candidates < 2disables the tournament.config.researchclaw.example.yamlis not touched in this PR; happy to document the knobs there if you'd prefer them advertised.Testing
Baseline on
mainat the time of branching (e2e23c9) was2863 passed, 1 failed— the failure is a pre-existing timing-sensitive test,test_hitl_advanced.py::TestFileWait::test_poll_with_delayed_response, which passed on this run. The delta is the 36 new tests.New coverage: debate engine (role retry, split synthesis path, judge/synthesizer wiring), tournament engine (empty-candidate drop, candidate-count effective value, ranking parse), and reviewer construction (fallback to generator, independent provider resolution, Stage 18 provenance).
🤖 Generated with Claude Code